From 9103b52e01bc99a81422ae895c9f5ff8903665e6 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sun, 12 Oct 2025 11:18:53 -0400 Subject: [PATCH 001/150] refactor: replace fetching message with tool event indicator for chat history loading --- src/routes/chat/hooks/use-chat.ts | 11 ++++++++--- src/routes/chat/utils/handle-websocket-message.ts | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/routes/chat/hooks/use-chat.ts b/src/routes/chat/hooks/use-chat.ts index eb564413..7d6d8b81 100644 --- a/src/routes/chat/hooks/use-chat.ts +++ b/src/routes/chat/hooks/use-chat.ts @@ -17,7 +17,7 @@ import { logger } from '@/utils/logger'; import { apiClient } from '@/lib/api-client'; import { appEvents } from '@/lib/app-events'; import { createWebSocketMessageHandler, type HandleMessageDeps } from '../utils/handle-websocket-message'; -import { isConversationalMessage, addOrUpdateMessage, createUserMessage, handleRateLimitError, createAIMessage, type ChatMessage } from '../utils/message-helpers'; +import { isConversationalMessage, addOrUpdateMessage, createUserMessage, handleRateLimitError, createAIMessage, appendToolEvent, type ChatMessage } from '../utils/message-helpers'; import { sendWebSocketMessage } from '../utils/websocket-helpers'; import { initialStages as defaultStages, updateStage as updateStageHelper } from '../utils/project-stage-helpers'; import type { ProjectStage } from '../utils/project-stage-helpers'; @@ -468,8 +468,13 @@ export function useChat({ }); } else if (connectionStatus.current === 'idle') { setIsBootstrapping(false); - // Get existing progress - sendMessage(createAIMessage('fetching-chat', 'Fetching your previous chat...')); + // Show fetching indicator as a tool-event style message + setMessages(() => + appendToolEvent([], 'fetching-chat', { + name: 'fetching your latest conversations', + status: 'start', + }), + ); // Fetch existing agent connection details const response = await apiClient.connectToAgent(urlChatId); diff --git a/src/routes/chat/utils/handle-websocket-message.ts b/src/routes/chat/utils/handle-websocket-message.ts index b0d86628..18c32e09 100644 --- a/src/routes/chat/utils/handle-websocket-message.ts +++ b/src/routes/chat/utils/handle-websocket-message.ts @@ -261,8 +261,19 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { }, []); if (restoredMessages.length > 0) { - logger.debug('Replacing messages with conversation_state history:', restoredMessages.length); - setMessages(restoredMessages); + logger.debug('Merging conversation_state history with existing messages (preserving fetch indicator):', restoredMessages.length); + setMessages(prev => { + const hasFetching = prev.some(m => m.role === 'assistant' && m.conversationId === 'fetching-chat'); + let next = prev; + if (hasFetching) { + // Mark fetching tool-event as completed + next = appendToolEvent(next, 'fetching-chat', { name: 'fetching your latest conversations', status: 'success' }); + // Append restored messages after the fetch indicator + return [...next, ...restoredMessages]; + } + // Fallback: replace if no fetching indicator exists + return restoredMessages; + }); } break; } From 5ebb714087367724785f97991c9c5fbe9587e83a Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sun, 12 Oct 2025 11:48:41 -0400 Subject: [PATCH 002/150] feat: add conversation reset functionality with confirmation dialog --- src/routes/chat/chat.tsx | 80 ++++++++++++++++--- .../chat/utils/handle-websocket-message.ts | 8 ++ 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/src/routes/chat/chat.tsx b/src/routes/chat/chat.tsx index bf66d8fb..a6479005 100644 --- a/src/routes/chat/chat.tsx +++ b/src/routes/chat/chat.tsx @@ -10,7 +10,7 @@ import { ArrowRight, Image as ImageIcon } from 'react-feather'; import { useParams, useSearchParams, useNavigate } from 'react-router'; import { MonacoEditor } from '../../components/monaco-editor/monaco-editor'; import { AnimatePresence, motion } from 'framer-motion'; -import { Expand, Github, LoaderCircle, RefreshCw } from 'lucide-react'; +import { Expand, Github, LoaderCircle, RefreshCw, MoreHorizontal, RotateCcw } from 'lucide-react'; import { Blueprint } from './components/blueprint'; import { FileExplorer } from './components/file-explorer'; import { UserMessage, AIMessage } from './components/messages'; @@ -34,6 +34,10 @@ import { useImageUpload } from '@/hooks/use-image-upload'; import { useDragDrop } from '@/hooks/use-drag-drop'; import { ImageAttachmentPreview } from '@/components/image-attachment-preview'; import { createAIMessage } from './utils/message-helpers'; +import { Button } from '@/components/ui/button'; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; +import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog'; +import { sendWebSocketMessage } from './utils/websocket-helpers'; export default function Chat() { const { chatId: urlChatId } = useParams(); @@ -153,6 +157,8 @@ export default function Chat() { const [debugMessages, setDebugMessages] = useState([]); const deploymentControlsRef = useRef(null); + const [isResetDialogOpen, setIsResetDialogOpen] = useState(false); + // Model config info state const [modelConfigs, setModelConfigs] = useState<{ agents: Array<{ key: string; name: string; description: string; }>; @@ -241,6 +247,12 @@ export default function Chat() { setView(mode); }, []); + const handleResetConversation = useCallback(() => { + if (!websocket) return; + sendWebSocketMessage(websocket, 'clear_conversation'); + setIsResetDialogOpen(false); + }, [websocket]); + // // Terminal functions // const handleTerminalCommand = useCallback((command: string) => { // if (websocket && websocket.readyState === WebSocket.OPEN) { @@ -519,11 +531,11 @@ export default function Chat() { ) : ( <> - {appTitle && ( -
- {appTitle} -
- )} + {(appTitle || chatId) && ( +
+
{appTitle}
+
+ )} @@ -543,11 +555,40 @@ export default function Chat() { )} {mainMessage && ( - +
+ + {chatId && ( +
+ + + + + + { + e.preventDefault(); + setIsResetDialogOpen(true); + }} + > + + Reset conversation + + + +
+ )} +
)} + + + + Reset conversation? + + This will clear the chat history for this app. Generated files and preview are not affected. + + + + Cancel + + Reset + + + + + {/* GitHub Export Modal */} appendToolEvent([], 'conversation_cleared', { + name: message.message || 'conversation reset', + status: 'success' + })); + break; + } case 'cf_agent_state': { const { state } = message; logger.debug('🔄 Agent state update received:', state); From 866c881612adcbf1f658d63d20caeddee42c6602 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Mon, 13 Oct 2025 11:53:33 -0400 Subject: [PATCH 003/150] feat: more prompt tweaks for react/frontend --- worker/agents/operations/PhaseGeneration.ts | 10 ++- .../agents/operations/PhaseImplementation.ts | 69 +++++++++---------- worker/agents/planning/blueprint.ts | 4 +- worker/agents/prompts.ts | 53 ++++++++++++-- wrangler.jsonc | 8 +-- 5 files changed, 96 insertions(+), 48 deletions(-) diff --git a/worker/agents/operations/PhaseGeneration.ts b/worker/agents/operations/PhaseGeneration.ts index 9541d144..4cca09c9 100644 --- a/worker/agents/operations/PhaseGeneration.ts +++ b/worker/agents/operations/PhaseGeneration.ts @@ -55,6 +55,8 @@ const SYSTEM_PROMPT = ` ${STRATEGIES.FRONTEND_FIRST_PLANNING} +${PROMPT_UTILS.UI_NON_NEGOTIABLES_V3} + ${PROMPT_UTILS.UI_GUIDELINES} ${PROMPT_UTILS.COMMON_DEP_DOCUMENTATION} @@ -110,12 +112,18 @@ Adhere to the following guidelines: - Each phase should work towards achieving the final product. **ONLY** mark as last phase if you are sure the project is at least 90-95% finished. - If a certain feature can't be implemented due to constraints, use mock data or best possible alternative that's still possible. - Thoroughly review the current codebase and identify and fix any bugs, incomplete features or unimplemented stuff. -• **BEAUTIFUL UI PRIORITY**: Next phase should cover fixes (if any), development, AND significant focus on creating visually stunning, professional-grade UI/UX with: +• **BEAUTIFUL UI PRIORITY**: Next phase should cover fixes (if any), development, AND significant focus on creating visually stunning, professional-grade UI/UX with: - Modern design patterns and visual hierarchy - Smooth animations and micro-interactions - Beautiful color schemes and typography - Proper spacing, shadows, and visual polish - Engaging user interface elements + + **UI LAYOUT NON-NEGOTIABLES (Tailwind v3-safe, shadcn/ui first)** + - Every page MUST wrap visible content in a root container with: max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 + - Use vertical section spacing: py-8 md:py-10 lg:py-12 across major content blocks + - Prefer shadcn/ui components for structure (e.g., Sidebar, Sheet, Card, Button) and compose with Tailwind utilities + - In each page file you modify/create, explicitly apply this structure and mention it in the file description • Use the section to guide your phase generation. • Ensure the next phase logically and iteratively builds on the previous one, maintaining visual excellence with modern design patterns, smooth interactions, and professional UI polish. • Provide a clear, concise, to the point description of the next phase and the purpose and contents of each file in it. diff --git a/worker/agents/operations/PhaseImplementation.ts b/worker/agents/operations/PhaseImplementation.ts index 00f2f073..740fbf13 100644 --- a/worker/agents/operations/PhaseImplementation.ts +++ b/worker/agents/operations/PhaseImplementation.ts @@ -72,6 +72,8 @@ export const SYSTEM_PROMPT = ` • If you see any other dependency being referenced, Immediately correct it. +${PROMPT_UTILS.UI_NON_NEGOTIABLES_V3} + ${PROMPT_UTILS.UI_GUIDELINES} We follow the following strategy at our team for rapidly delivering projects: @@ -116,7 +118,11 @@ These are the instructions and quality standards that must be followed to implem - Never call setState during render phase - Always use dependency arrays in useEffect with conditional guards - Stabilize object/array references with useMemo/useCallback - - **Zustand: Select ONLY primitives individually OR use useShallow wrapper** + - **Zustand: Select ONLY primitives individually** + - Never use the store without a selector (selecting whole state is forbidden) + - Do not allocate objects/arrays or call store methods inside selectors + - If selecting an object, only store-owned and stable with shallow equality (no allocation) + - DOM listeners must be stable: attach once; read store values via refs; avoid reattaching per state change 2. **Variable Declaration Order** - CRITICAL - Declare/import ALL variables before use @@ -134,42 +140,34 @@ These are the instructions and quality standards that must be followed to implem - Use try-catch for async operations - Handle undefined values gracefully - 5. Layout Architecture Requirements (MANDATORY, copy these patterns) - - Full-height page layout: -
-
...
-
...
-
- - - Sidebar + main layout (Finder/IDE/Dashboard): -
- -
...
-
- Notes: - - Always give the sidebar a min-width via CSS (min-w-[180px]) to prevent text cutoff. - - Prefer CSS min-w on content instead of relying on % minimums. - - - Resizable panels (horizontal): - - - - - - -
...
-
-
- Notes: - - Parent must have explicit height (h-full / h-screen). - - Put a ResizableHandle between panels. - - Use CSS min-w-[...] on the sidebar content to guarantee readable width. + 5. UI Layout Non-Negotiables (Tailwind v3-safe) + - Root wrapper must include: max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 + - Use vertical section spacing between major blocks: py-8 md:py-10 lg:py-12 + - Prefer shadcn/ui components for structure and widgets; compose with Tailwind utilities + - Do NOT use CSS @theme or CSS @plugin directives in component styles + - For media sizing, prefer aspect-video or aspect-[16/9] and object-cover + + Contrast Self-Check (light theme) + - Dark background without light foreground? Use paired *-foreground (e.g., bg-accent text-accent-foreground) + - Primary labels must use text-foreground; avoid text-muted-foreground on primary items + - Chat inputs align to light theme: bg-secondary text-secondary-foreground placeholder:text-muted-foreground + + Optional Patterns & Common Layouts (use as needed) + - Sidebar layout: Use shadcn/ui Sidebar primitives + • Prefer , , and related primitives from "@/components/ui/sidebar" + • Avoid custom sidebars; compose with Tailwind utilities and apply min-w-[180px] to prevent text cutoff + + - Resizable panels: Use shadcn/ui Resizable primitives + • Based on react-resizable-panels (already installed in template) + • Ensure parent has explicit height (h-full/h-screen) and include a handle between panels + + - Full-height pages: Use flex column with header/footer as needed; keep
scrollable (flex-1 overflow-auto) - Data-driven rendering (always guard): - if (isLoading) return ; - if (error) return ; - if (!items?.length) return ; - return ; + if (isLoading) return ; + if (error) return ; + if (!items?.length) return ; + return ; 6. Framer Motion Drag Handle Policy (correct API usage) - Framer Motion does NOT support a dragHandle prop. @@ -280,6 +278,7 @@ Every single file listed in needs to be implemented in this phas - NEVER call setState during render phase - ALWAYS use proper dependency arrays with conditional guards - Validate code before submitting: search for forbidden patterns listed above + - Scan for: useStore(s => ({, useStore(), useStore(s => s.get and rewrite immediately ⚠️ **BACKWARD COMPATIBILITY** - PRESERVE EXISTING FUNCTIONALITY - Do NOT break anything from previous phases diff --git a/worker/agents/planning/blueprint.ts b/worker/agents/planning/blueprint.ts index c8aec8bd..3f0adb42 100644 --- a/worker/agents/planning/blueprint.ts +++ b/worker/agents/planning/blueprint.ts @@ -58,6 +58,8 @@ const SYSTEM_PROMPT = ` - Mobile-first responsive design that scales beautifully ** Lay these visual design instructions out explicitly throughout the blueprint ** + ${PROMPT_UTILS.UI_NON_NEGOTIABLES_V3} + ${PROMPT_UTILS.UI_GUIDELINES} ## Frameworks & Dependencies @@ -131,7 +133,7 @@ const SYSTEM_PROMPT = ` - **Icons:** lucide-react, @radix-ui/react-icons, heroicons - **Visual Effects:** react-intersection-observer, react-parallax - **Charts/Data Viz:** recharts, @tremor/react (if data visualization needed) - - **Media/Images:** next/image optimizations, react-image-gallery + - **Media/Images:** react-image-gallery or vanilla ; prefer aspect-video / aspect-[16/9] and object-cover; avoid Next.js-only APIs Suggest whatever additional frameworks are needed to achieve visual excellence. diff --git a/worker/agents/prompts.ts b/worker/agents/prompts.ts index 1b5a6892..79c16f93 100644 --- a/worker/agents/prompts.ts +++ b/worker/agents/prompts.ts @@ -602,9 +602,12 @@ const a = useStore(s => s.a); const b = useStore(s => s.b); const c = useStore(s => s.c); -// Option 2: useShallow wrapper (advanced, only if needed) -import { useShallow } from 'zustand/react/shallow'; -const { a, b, c } = useStore(useShallow(s => ({ a: s.a, b: s.b, c: s.c }))); +// Option 2: shallow equality for STABLE objects (advanced; only if the object +// reference comes directly from the store and does not get re-created). Do NOT +// allocate objects in the selector. +import { shallow } from 'zustand/shallow'; +const viewport = useStore(s => s.viewport, shallow); // ✅ stable object from store +// ❌ BAD: const v = useStore(s => ({ ...s.viewport })); // allocates new object // Option 3: Store methods → Select primitives + useMemo in component const items = useStore(s => s.items); @@ -679,6 +682,7 @@ const handleClick = useCallback(() => setCount(prev => prev + 1), []); ✅ **Functional updates** - \`setState(prev => prev + 1)\` for correctness ✅ **useRef for non-UI data** - Doesn't trigger re-renders ✅ **Derive, don't mirror** - \`const upper = prop.toUpperCase()\` not useState +✅ **DOM listeners stable** - Keep effect deps static; read live store values via refs; do not reattach listeners on every state change **QUICK VALIDATION BEFORE SUBMITTING CODE:** → Search for: \`useStore(s => ({\`, \`useStore(s => s.get\`, \`useStore()\` @@ -767,7 +771,7 @@ COMMON_PITFALLS: ` **FRAMEWORK & SYNTAX SPECIFICS:** • Framework compatibility: Pay attention to version differences (Tailwind v3 vs v4, React Router versions) • No environment variables: App deploys serverless - avoid libraries requiring env vars unless they support defaults - • Next.js best practices: Follow latest patterns to prevent dev server rendering issues + • React/Vite best practices: Follow patterns compatible with Vite + React (avoid Next.js-specific APIs) • Tailwind classes: Verify all classes exist in tailwind.config.js (e.g., avoid undefined classes like \`border-border\`) • Component exports: Export all components properly, avoid mixing default/named imports • UI spacing: Ensure proper padding/margins, avoid left-aligned layouts without proper spacing @@ -1030,7 +1034,7 @@ bun add @geist-ui/react@1 • **Mobile-First Excellence:** Design for mobile, enhance for desktop: - **Touch Targets:** Minimum 44px touch targets for mobile usability - **Typography Scaling:** text-2xl md:text-4xl lg:text-5xl for responsive headers - - **Image Handling:** aspect-w-16 aspect-h-9 for consistent image ratios + - **Image Handling:** Prefer Tailwind v3-safe utilities like aspect-video or aspect-[16/9] for consistent image ratios • **Breakpoint Strategy:** Use Tailwind breakpoints meaningfully: - **sm (640px):** Tablet portrait adjustments - **md (768px):** Tablet landscape and small desktop @@ -1048,7 +1052,42 @@ bun add @geist-ui/react@1 - ✅ **Empty State Beauty:** Inspiring empty states that guide users toward their first success - ✅ **Accessibility Excellence:** Proper contrast ratios, keyboard navigation, screen reader support - ✅ **Performance Smooth:** 60fps animations and instant perceived load times`, - PROJECT_CONTEXT: `Here is everything you will need about the project: + UI_NON_NEGOTIABLES_V3: `## UI NON-NEGOTIABLES (Tailwind v3-safe, shadcn/ui first) + +1) Root Wrapper & Gutters (copy exactly) +export default function Page() { + return ( +
+
+ {/* content */} +
+
+ ); +} + +2) Prefer shadcn/ui components heavily +- Use shadcn/ui primitives for structure and widgets (e.g., Button, Card, Input, Sheet, Sidebar) +- Import from "@/components/ui/..." and compose with Tailwind utilities. Use Radix primitives as needed for composition. + +3) Tailwind v3-safe Instructions +- Avoid CSS @theme or CSS @plugin directives in component styles +- Prefer built-in utilities only; avoid plugin-only utilities unless template shows they exist +- For media sizing, prefer aspect-video or aspect-[16/9] and object-cover + +4) Good vs Bad +- BAD: top-level
with no gutters (content flush to the left edge) +- GOOD: wrap with max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 and section spacing py-8 md:py-10 lg:py-12 + +5) Color & Contrast (light theme defaults) +- Primary labels: text-foreground +- Secondary/meta: text-muted-foreground +- Selected state: bg-accent text-accent-foreground (or bg-primary text-primary-foreground) +- Icons: text-foreground/80 hover:text-foreground +- Inputs: bg-secondary text-secondary-foreground border border-input; placeholder:text-muted-foreground +- Never place muted text over dark backgrounds; if background is dark, use paired *-foreground or text-white +- Aim for >= 4.5:1 contrast for normal text (>= 3:1 for large) +`, +PROJECT_CONTEXT: `Here is everything you will need about the project: @@ -1297,7 +1336,7 @@ ${runtimeErrorsText || 'No runtime errors detected'} ${staticAnalysisText} ## ANALYSIS INSTRUCTIONS -- **PRIORITIZE** "Maximum update depth exceeded" and useEffect-related errors +- **PRIORITIZE** "Maximum update depth exceeded" and useEffect-related errors. If 'Warning: The result of getSnapshot should be cached to avoid an infinite loop' is present, it is a high priority issue to be resolved ASAP. - **CROSS-REFERENCE** error messages with current code structure (line numbers may be outdated) - **VALIDATE** reported issues against actual code patterns before fixing - **FOCUS** on deployment-blocking runtime errors over linting issues` diff --git a/wrangler.jsonc b/wrangler.jsonc index c7f5f576..0a20ab59 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -64,12 +64,12 @@ "image": "./SandboxDockerfile", // "image": "registry.cloudflare.com/vibesdk-production-userappsandboxservice:cfe197fc", // Altering max_instances value will have no effect. Please use the MAX_SANDBOX_INSTANCES var instead. - "max_instances": 2900, + "max_instances": 1400, // ATTENTION: Altering instance_type value will have no effect. Please use the SANDBOX_INSTANCE_TYPE var instead. "instance_type": { "vcpu": 4, - "memory_mib": 4096, - "disk_mb": 6144 + "memory_mib": 8192, + "disk_mb": 10240 }, "rollout_step_percentage": 100 } @@ -151,7 +151,7 @@ "CUSTOM_DOMAIN": "", "MAX_SANDBOX_INSTANCES": "10", "SANDBOX_INSTANCE_TYPE": "standard-3", - "USE_CLOUDFLARE_IMAGES": false, + "USE_CLOUDFLARE_IMAGES": "" }, "workers_dev": false, "preview_urls": false From 5e1d76d8f8fc7d0c17d50df9bfbbe0a98dd59a87 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Mon, 13 Oct 2025 11:53:57 -0400 Subject: [PATCH 004/150] refactor: rename FastCodeFixer to PostPhaseCodeFixer and fix import path --- .../operations/{FastCodeFixer.ts => PostPhaseCodeFixer.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename worker/agents/operations/{FastCodeFixer.ts => PostPhaseCodeFixer.ts} (98%) diff --git a/worker/agents/operations/FastCodeFixer.ts b/worker/agents/operations/PostPhaseCodeFixer.ts similarity index 98% rename from worker/agents/operations/FastCodeFixer.ts rename to worker/agents/operations/PostPhaseCodeFixer.ts index e5d50fab..bb3de4e2 100644 --- a/worker/agents/operations/FastCodeFixer.ts +++ b/worker/agents/operations/PostPhaseCodeFixer.ts @@ -1,7 +1,7 @@ import { createSystemMessage, createUserMessage } from '../inferutils/common'; import { executeInference } from '../inferutils/infer'; import { PROMPT_UTILS } from '../prompts'; -import { AgentOperation, OperationOptions } from '../operations/common'; +import { AgentOperation, OperationOptions } from './common'; import { FileOutputType, PhaseConceptType } from '../schemas'; import { SCOFFormat } from '../output-formats/streaming-formats/scof'; import { CodeIssue } from '../../services/sandbox/sandboxTypes'; From db5c8451ee37f9c0a35c83692ee77966a9fd3394 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Mon, 13 Oct 2025 11:54:12 -0400 Subject: [PATCH 005/150] chore: adjust rate limits for agent and llm calls --- worker/services/rate-limit/config.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/worker/services/rate-limit/config.ts b/worker/services/rate-limit/config.ts index acc929b9..52cf3fcc 100644 --- a/worker/services/rate-limit/config.ts +++ b/worker/services/rate-limit/config.ts @@ -75,15 +75,15 @@ export const DEFAULT_RATE_LIMIT_SETTINGS: RateLimitSettings = { enabled: true, store: RateLimitStore.DURABLE_OBJECT, limit: 10, - dailyLimit: 50, - period: 3600, // 1 hour + dailyLimit: 10, + period: 4 * 60 * 60, // 4 hour }, llmCalls: { enabled: true, store: RateLimitStore.DURABLE_OBJECT, - limit: 100, + limit: 400, period: 60 * 60, // 1 hour - dailyLimit: 400, + dailyLimit: 1000, excludeBYOKUsers: true, }, }; From 871548febfdcf6efd5d3fade563f8a7738f64e08 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Wed, 15 Oct 2025 23:59:08 -0400 Subject: [PATCH 006/150] feat: implement project name and blueprint update tools --- worker/agents/constants.ts | 2 + .../agents/tools/toolkit/alter-blueprint.ts | 53 +++++++++++++++++++ .../tools/toolkit/clear-conversation.ts | 27 ++++++++++ worker/agents/tools/toolkit/rename-project.ts | 43 +++++++++++++++ worker/api/websocketTypes.ts | 14 +++++ worker/services/sandbox/BaseSandboxService.ts | 2 + .../services/sandbox/remoteSandboxService.ts | 4 ++ worker/services/sandbox/sandboxSdkClient.ts | 17 ++++++ 8 files changed, 162 insertions(+) create mode 100644 worker/agents/tools/toolkit/alter-blueprint.ts create mode 100644 worker/agents/tools/toolkit/clear-conversation.ts create mode 100644 worker/agents/tools/toolkit/rename-project.ts diff --git a/worker/agents/constants.ts b/worker/agents/constants.ts index 0aa3d368..ad96e369 100644 --- a/worker/agents/constants.ts +++ b/worker/agents/constants.ts @@ -61,6 +61,8 @@ export const WebSocketMessageResponses: Record = { CONVERSATION_RESPONSE: 'conversation_response', CONVERSATION_CLEARED: 'conversation_cleared', CONVERSATION_STATE: 'conversation_state', + PROJECT_NAME_UPDATED: 'project_name_updated', + BLUEPRINT_UPDATED: 'blueprint_updated', // Model configuration info MODEL_CONFIGS_INFO: 'model_configs_info', diff --git a/worker/agents/tools/toolkit/alter-blueprint.ts b/worker/agents/tools/toolkit/alter-blueprint.ts new file mode 100644 index 00000000..d4e5faa5 --- /dev/null +++ b/worker/agents/tools/toolkit/alter-blueprint.ts @@ -0,0 +1,53 @@ +import { ToolDefinition } from '../types'; +import { StructuredLogger } from '../../../logger'; +import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; +import { Blueprint } from 'worker/agents/schemas'; + +type AlterBlueprintArgs = { + patch: Partial & { + projectName?: string; + }; +}; + +export function createAlterBlueprintTool( + agent: CodingAgentInterface, + logger: StructuredLogger +): ToolDefinition { + return { + type: 'function' as const, + function: { + name: 'alter_blueprint', + description: 'Apply a validated patch to the current blueprint. Only allowed keys are accepted.', + parameters: { + type: 'object', + additionalProperties: false, + properties: { + patch: { + type: 'object', + additionalProperties: false, + properties: { + title: { type: 'string' }, + projectName: { type: 'string', minLength: 3, maxLength: 50, pattern: '^[a-z0-9-_]+$' }, + detailedDescription: { type: 'string' }, + description: { type: 'string' }, + colorPalette: { type: 'array', items: { type: 'string' } }, + views: { type: 'array', items: { type: 'object', additionalProperties: false, properties: { name: { type: 'string' }, description: { type: 'string' } }, required: ['name', 'description'] } }, + userFlow: { type: 'object', additionalProperties: false, properties: { uiLayout: { type: 'string' }, uiDesign: { type: 'string' }, userJourney: { type: 'string' } } }, + dataFlow: { type: 'string' }, + architecture: { type: 'object', additionalProperties: false, properties: { dataFlow: { type: 'string' } } }, + pitfalls: { type: 'array', items: { type: 'string' } }, + frameworks: { type: 'array', items: { type: 'string' } }, + implementationRoadmap: { type: 'array', items: { type: 'object', additionalProperties: false, properties: { phase: { type: 'string' }, description: { type: 'string' } }, required: ['phase', 'description'] } }, + }, + }, + }, + required: ['patch'], + }, + }, + implementation: async (args) => { + logger.info('Altering blueprint', { keys: Object.keys(args.patch) }); + const updated = await agent.updateBlueprint(args.patch); + return updated; + }, + }; +} diff --git a/worker/agents/tools/toolkit/clear-conversation.ts b/worker/agents/tools/toolkit/clear-conversation.ts new file mode 100644 index 00000000..5ac0a822 --- /dev/null +++ b/worker/agents/tools/toolkit/clear-conversation.ts @@ -0,0 +1,27 @@ +import { ToolDefinition } from '../types'; +import { StructuredLogger } from '../../../logger'; +import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; + +export function createClearConversationTool( + agent: CodingAgentInterface, + logger: StructuredLogger +): ToolDefinition, null> { + return { + type: 'function' as const, + function: { + name: 'clear_conversation', + description: 'Clear the current conversation history for this session.', + parameters: { + type: 'object', + properties: {}, + additionalProperties: false, + required: [], + }, + }, + implementation: async () => { + logger.info('Clearing conversation history'); + agent.clearConversation(); + return null; + }, + }; +} diff --git a/worker/agents/tools/toolkit/rename-project.ts b/worker/agents/tools/toolkit/rename-project.ts new file mode 100644 index 00000000..be7ab416 --- /dev/null +++ b/worker/agents/tools/toolkit/rename-project.ts @@ -0,0 +1,43 @@ +import { ToolDefinition } from '../types'; +import { StructuredLogger } from '../../../logger'; +import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; + +type RenameArgs = { + newName: string; +}; + +type RenameResult = { projectName: string }; + +export function createRenameProjectTool( + agent: CodingAgentInterface, + logger: StructuredLogger +): ToolDefinition { + return { + type: 'function' as const, + function: { + name: 'rename_project', + description: 'Rename the project. Lowercase letters, numbers, hyphens, and underscores only. No spaces or dots. Call this alongside queue_request tool to update the codebase', + parameters: { + type: 'object', + additionalProperties: false, + properties: { + newName: { + type: 'string', + minLength: 3, + maxLength: 50, + pattern: '^[a-z0-9-_]+$' + }, + }, + required: ['newName'], + }, + }, + implementation: async (args) => { + logger.info('Renaming project', { newName: args.newName }); + const ok = await agent.updateProjectName(args.newName); + if (!ok) { + throw new Error('Failed to rename project'); + } + return { projectName: args.newName }; + }, + }; +} diff --git a/worker/api/websocketTypes.ts b/worker/api/websocketTypes.ts index 3eff6f02..9874f03c 100644 --- a/worker/api/websocketTypes.ts +++ b/worker/api/websocketTypes.ts @@ -315,6 +315,18 @@ type ConversationClearedMessage = { clearedMessageCount: number; }; +type ProjectNameUpdatedMessage = { + type: 'project_name_updated'; + message: string; + projectName: string; +}; + +type BlueprintUpdatedMessage = { + type: 'blueprint_updated'; + message: string; + updatedKeys: string[]; +}; + type DeterministicCodeFixStartedMessage = { type: 'deterministic_code_fix_started'; message: string; @@ -419,6 +431,8 @@ export type WebSocketMessage = | UserSuggestionsProcessingMessage | ConversationResponseMessage | ConversationClearedMessage + | ProjectNameUpdatedMessage + | BlueprintUpdatedMessage | DeterministicCodeFixStartedMessage | DeterministicCodeFixCompletedMessage | ModelConfigsInfoMessage diff --git a/worker/services/sandbox/BaseSandboxService.ts b/worker/services/sandbox/BaseSandboxService.ts index ab16d509..8fd1a597 100644 --- a/worker/services/sandbox/BaseSandboxService.ts +++ b/worker/services/sandbox/BaseSandboxService.ts @@ -178,6 +178,8 @@ import { FileOutputType } from 'worker/agents/schemas'; * Returns: { success: boolean, results: [...], message?: string, error?: string } */ abstract executeCommands(instanceId: string, commands: string[], timeout?: number): Promise; + + abstract updateProjectName(instanceId: string, projectName: string): Promise; // ========================================== // ERROR MANAGEMENT (Required) diff --git a/worker/services/sandbox/remoteSandboxService.ts b/worker/services/sandbox/remoteSandboxService.ts index caa85baa..119e4499 100644 --- a/worker/services/sandbox/remoteSandboxService.ts +++ b/worker/services/sandbox/remoteSandboxService.ts @@ -234,6 +234,10 @@ export class RemoteSandboxServiceClient extends BaseSandboxService{ return this.makeRequest('/instances', 'GET'); } + async updateProjectName(instanceId: string, projectName: string): Promise { + return this.makeRequest(`/instances/${instanceId}/name`, 'POST', undefined, { projectName }); + } + /** * Get logs from a runner instance */ diff --git a/worker/services/sandbox/sandboxSdkClient.ts b/worker/services/sandbox/sandboxSdkClient.ts index 1f708b8b..75040ab5 100644 --- a/worker/services/sandbox/sandboxSdkClient.ts +++ b/worker/services/sandbox/sandboxSdkClient.ts @@ -164,6 +164,23 @@ export class SandboxSdkClient extends BaseSandboxService { } } + async updateProjectName(instanceId: string, projectName: string): Promise { + try { + await this.updateProjectConfiguration(instanceId, projectName); + try { + const metadata = await this.getInstanceMetadata(instanceId); + const updated = { ...metadata, projectName } as InstanceMetadata; + await this.storeInstanceMetadata(instanceId, updated); + } catch (error) { + this.logger.error('Failed to update instance metadata', error); + } + return true; + } catch (error) { + this.logger.error('Failed to update project name', error); + return false; + } + } + private getInstanceMetadataFile(instanceId: string): string { return `${instanceId}-metadata.json`; } From dcf98a4240447f79d5612d8f92284902fc144846 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 16 Oct 2025 00:09:25 -0400 Subject: [PATCH 007/150] fix: use correct error field in deployment failure toast message --- src/routes/chat/utils/handle-websocket-message.ts | 2 +- worker/api/websocketTypes.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/chat/utils/handle-websocket-message.ts b/src/routes/chat/utils/handle-websocket-message.ts index a0b7f26d..b29fb5b8 100644 --- a/src/routes/chat/utils/handle-websocket-message.ts +++ b/src/routes/chat/utils/handle-websocket-message.ts @@ -354,7 +354,7 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { } case 'deployment_failed': { - toast.error(`Error: ${message.message}`); + toast.error(message.error); break; } diff --git a/worker/api/websocketTypes.ts b/worker/api/websocketTypes.ts index 9874f03c..228c7486 100644 --- a/worker/api/websocketTypes.ts +++ b/worker/api/websocketTypes.ts @@ -75,7 +75,7 @@ type DeploymentStartedMessage = { type DeploymentFailedMessage = { type: 'deployment_failed'; - message: string; + error: string; }; type DeploymentCompletedMessage = { From b76d000b919ddf4cdeb36891afa6078325f8e59c Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 16 Oct 2025 00:45:53 -0400 Subject: [PATCH 008/150] fix: prevent null ruleId in ESLint diagnostic messages by providing empty string fallback --- worker/services/sandbox/sandboxSdkClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker/services/sandbox/sandboxSdkClient.ts b/worker/services/sandbox/sandboxSdkClient.ts index 75040ab5..3af2ab1e 100644 --- a/worker/services/sandbox/sandboxSdkClient.ts +++ b/worker/services/sandbox/sandboxSdkClient.ts @@ -1650,7 +1650,7 @@ export class SandboxSdkClient extends BaseSandboxService { line: message.line || 0, column: message.column, severity: this.mapESLintSeverity(message.severity), - ruleId: message.ruleId, + ruleId: message.ruleId || '', source: 'eslint' }); } From 520c2f61fc5d6a012c45a5cb487fc728e2be3fb4 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 16 Oct 2025 02:39:00 -0400 Subject: [PATCH 009/150] feat: initial implementation of deep debugging agent --- worker/agents/assistants/codeDebugger.ts | 136 +++++++++++++++ worker/agents/constants.ts | 3 + worker/agents/core/simpleGeneratorAgent.ts | 162 ++++++++++++++++-- .../operations/UserConversationProcessor.ts | 45 ++++- .../services/implementations/CodingAgent.ts | 39 ++++- .../services/interfaces/ICodingAgent.ts | 21 ++- worker/agents/tools/customTools.ts | 41 ++++- worker/agents/tools/toolkit/deep-debugger.ts | 71 ++++++++ worker/agents/tools/toolkit/deploy-preview.ts | 2 +- worker/agents/tools/toolkit/exec-commands.ts | 49 ++++++ worker/agents/tools/toolkit/read-files.ts | 45 +++++ .../agents/tools/toolkit/regenerate-file.ts | 50 ++++++ worker/agents/tools/toolkit/run-analysis.ts | 37 ++++ worker/api/websocketTypes.ts | 16 ++ 14 files changed, 694 insertions(+), 23 deletions(-) create mode 100644 worker/agents/assistants/codeDebugger.ts create mode 100644 worker/agents/tools/toolkit/deep-debugger.ts create mode 100644 worker/agents/tools/toolkit/exec-commands.ts create mode 100644 worker/agents/tools/toolkit/read-files.ts create mode 100644 worker/agents/tools/toolkit/regenerate-file.ts create mode 100644 worker/agents/tools/toolkit/run-analysis.ts diff --git a/worker/agents/assistants/codeDebugger.ts b/worker/agents/assistants/codeDebugger.ts new file mode 100644 index 00000000..011650c6 --- /dev/null +++ b/worker/agents/assistants/codeDebugger.ts @@ -0,0 +1,136 @@ +import Assistant from './assistant'; +import { + createAssistantMessage, + createSystemMessage, + createUserMessage, + Message, +} from '../inferutils/common'; +import { executeInference } from '../inferutils/infer'; +import { InferenceContext, ModelConfig } from '../inferutils/config.types'; +import { createObjectLogger } from '../../logger'; +import type { ToolDefinition } from '../tools/types'; +import { CodingAgentInterface } from '../services/implementations/CodingAgent'; +import { AGENT_CONFIG } from '../inferutils/config'; +import { buildDebugTools } from '../tools/customTools'; +import { RenderToolCall } from '../operations/UserConversationProcessor'; + +const SYSTEM_PROMPT = `You are an autonomous code debugging assistant. +Goal: find root-cause fast and apply minimal, surgical fixes. + +Use tools to: +- get_logs: fetch runtime errors +- run_analysis: lint + typecheck (optionally scoped to files) +- read_files: read file contents by RELATIVE paths (batch multiple files in one call) +- exec_commands: run shell commands from project root (no cd needed) +- regenerate_file: apply surgical fixes to specific files +- deploy_preview: redeploy when fixes are applied + +Context rules (IMPORTANT): +- All file paths are RELATIVE to the project root (sandbox pwd = project directory) +- Commands execute from project root automatically (no cd needed) +- Prefer batching/parallel tool calls (e.g., read multiple files together) + +Guardrails: +- Prevent React render loops (state-in-render, missing deps, unstable Zustand selectors) +- Ensure import/export integrity (named vs default) + +Strategy: get_logs/run_analysis → read suspect files → apply fixes → verify → repeat until clean +Keep output concise. Act decisively. Stop when errors are cleared or stuck.`; + +const USER_PROMPT = (issue: string, fileSummaries: string) => `Issue to debug: +${issue} + +Project files (metadata only): +${fileSummaries} + +Strategy: +1) get_logs, run_analysis (scope to suspect files when known) +2) locate suspect files; read_files only when needed (RELATIVE paths, batch reads) +3) if fix needed, call regenerate_file with precise issues (minimal diff) +4) recheck errors/analysis; repeat until clean or stuck +Reply concisely with steps taken, findings, and results.`; + +export type FileIndexEntry = { + path: string; + purpose?: string; + changes?: string | null; +}; + +export type DebugSession = { + filesIndex: FileIndexEntry[]; + agent: CodingAgentInterface; +}; + +export type DebugInputs = { + issue: string; +}; + +function summarizeFiles(files: FileIndexEntry[], max = 120): string { + const compact = files + .slice(0, max) + .map( + (f) => + `- ${f.path}${f.purpose ? ` — ${f.purpose}` : ''}${f.changes ? ` (changes: ${truncate(f.changes, 80)})` : ''}`, + ) + .join('\n'); + const extra = + files.length > max ? `\n...and ${files.length - max} more` : ''; + return compact + extra; +} + +function truncate(s?: string | null, n: number = 120): string { + if (!s) return ''; + return s.length > n ? s.slice(0, n) + '…' : s; +} +export class DeepCodeDebugger extends Assistant { + logger = createObjectLogger(this, 'DeepCodeDebugger'); + modelConfigOverride?: ModelConfig; + + constructor( + env: Env, + inferenceContext: InferenceContext, + modelConfigOverride?: ModelConfig, + ) { + super(env, inferenceContext); + this.modelConfigOverride = modelConfigOverride; + } + + async run( + inputs: DebugInputs, + session: DebugSession, + streamCb?: (chunk: string) => void, + toolRenderer?: RenderToolCall, + ): Promise { + const fileSummaries = summarizeFiles(session.filesIndex); + const system = createSystemMessage(SYSTEM_PROMPT); + const user = createUserMessage( + USER_PROMPT(inputs.issue, fileSummaries), + ); + const messages: Message[] = this.save([system, user]); + + const logger = this.logger; + const tools: ToolDefinition[] = buildDebugTools( + session, + logger, + toolRenderer, + ); + + const result = await executeInference({ + env: this.env, + context: this.inferenceContext, + agentActionName: 'conversationalResponse', + modelConfig: this.modelConfigOverride || AGENT_CONFIG.codeReview, + messages, + tools, + stream: streamCb + ? { chunk_size: 64, onChunk: (c) => streamCb(c) } + : undefined, + temperature: 0.0, + reasoning_effort: 'low', + }); + + const out = result?.string || ''; + this.save([createAssistantMessage(out)]); + return out; + } +} diff --git a/worker/agents/constants.ts b/worker/agents/constants.ts index ad96e369..89953973 100644 --- a/worker/agents/constants.ts +++ b/worker/agents/constants.ts @@ -41,7 +41,10 @@ export const WebSocketMessageResponses: Record = { CODE_REVIEWING: 'code_reviewing', CODE_REVIEWED: 'code_reviewed', + COMMAND_EXECUTING: 'command_executing', + COMMAND_EXECUTED: 'command_executed', + COMMAND_EXECUTION_FAILED: 'command_execution_failed', // Generation control messages GENERATION_STOPPED: 'generation_stopped', diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index 585a7e1b..c5510590 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -33,7 +33,7 @@ import { InferenceContext, AgentActionKey } from '../inferutils/config.types'; import { AGENT_CONFIG } from '../inferutils/config'; import { ModelConfigService } from '../../database/services/ModelConfigService'; import { FileFetcher, fixProjectIssues } from '../../services/code-fixer'; -import { FastCodeFixerOperation } from '../operations/FastCodeFixer'; +import { FastCodeFixerOperation } from '../operations/PostPhaseCodeFixer'; import { getProtocolForHost } from '../../utils/urls'; import { looksLikeCommand } from '../utils/common'; import { generateBlueprint } from '../planning/blueprint'; @@ -335,11 +335,7 @@ export class SimpleCodeGeneratorAgent extends Agent { return this.getAgentId() ? true : false } - onStateUpdate(_state: CodeGenState, _source: "server" | Connection) { - // You can leave this empty to disable logging - // Or, you can log a more specific message, for example: - this.logger().info("State was updated."); - } + onStateUpdate(_state: CodeGenState, _source: "server" | Connection) {} setState(state: CodeGenState): void { try { @@ -425,7 +421,7 @@ export class SimpleCodeGeneratorAgent extends Agent { return this.state.phasesCounter; } - private getOperationOptions(): OperationOptions { + getOperationOptions(): OperationOptions { return { env: this.env, agentId: this.getAgentId(), @@ -1444,7 +1440,7 @@ export class SimpleCodeGeneratorAgent extends Agent { * Perform static code analysis on the generated files * This helps catch potential issues early in the development process */ - async runStaticAnalysisCode(): Promise { + async runStaticAnalysisCode(files?: string[]): Promise { const { sandboxInstanceId } = this.state; if (!sandboxInstanceId) { @@ -1454,10 +1450,12 @@ export class SimpleCodeGeneratorAgent extends Agent { this.logger().info(`Linting code in sandbox instance ${sandboxInstanceId}`); - const files = this.fileManager.getGeneratedFilePaths(); + const targetFiles = Array.isArray(files) && files.length > 0 + ? files + : this.fileManager.getGeneratedFilePaths(); try { - const analysisResponse = await this.getSandboxServiceClient()?.runStaticAnalysisCode(sandboxInstanceId, files); + const analysisResponse = await this.getSandboxServiceClient()?.runStaticAnalysisCode(sandboxInstanceId, targetFiles); if (!analysisResponse || analysisResponse.error) { const errorMsg = `Code linting failed: ${analysisResponse?.error || 'Unknown error'}, full response: ${JSON.stringify(analysisResponse)}`; @@ -1640,6 +1638,131 @@ export class SimpleCodeGeneratorAgent extends Agent { return { runtimeErrors, staticAnalysis, clientErrors }; } + async updateProjectName(newName: string): Promise { + try { + const valid = /^[a-z0-9-_]{3,50}$/.test(newName); + if (!valid) return false; + const updatedBlueprint = { ...this.state.blueprint, projectName: newName } as Blueprint; + this.setState({ + ...this.state, + blueprint: updatedBlueprint + }); + let ok = true; + if (this.state.sandboxInstanceId) { + try { + ok = await this.getSandboxServiceClient().updateProjectName(this.state.sandboxInstanceId, newName); + } catch (_) { + ok = false; + } + } + try { + const appService = new AppService(this.env); + const dbOk = await appService.updateApp(this.getAgentId(), { title: newName }); + ok = ok && dbOk; + } catch (error) { + this.logger().error('Error updating project name in database:', error); + ok = false; + } + this.broadcast(WebSocketMessageResponses.PROJECT_NAME_UPDATED, { + message: 'Project name updated', + projectName: newName + }); + return ok; + } catch (error) { + this.logger().error('Error updating project name:', error); + return false; + } + } + + async updateBlueprint(patch: Partial): Promise { + const keys = Object.keys(patch) as (keyof Blueprint)[]; + const allowed = new Set([ + 'title', + 'projectName', + 'detailedDescription', + 'description', + 'colorPalette', + 'views', + 'userFlow', + 'dataFlow', + 'architecture', + 'pitfalls', + 'frameworks', + 'implementationRoadmap' + ]); + const filtered: Partial = {}; + for (const k of keys) { + if (allowed.has(k) && typeof (patch as any)[k] !== 'undefined') { + (filtered as any)[k] = (patch as any)[k]; + } + } + if (typeof filtered.projectName === 'string' && filtered.projectName) { + await this.updateProjectName(filtered.projectName); + delete (filtered as any).projectName; + } + const updated: Blueprint = { ...this.state.blueprint, ...(filtered as Blueprint) } as Blueprint; + this.setState({ + ...this.state, + blueprint: updated + }); + this.broadcast(WebSocketMessageResponses.BLUEPRINT_UPDATED, { + message: 'Blueprint updated', + updatedKeys: Object.keys(filtered) + }); + return updated; + } + + // ===== Debugging helpers for assistants ===== + async readFiles(paths: string[]): Promise<{ files: { path: string; content: string }[] }> { + const { sandboxInstanceId } = this.state; + if (!sandboxInstanceId) { + return { files: [] }; + } + const resp = await this.getSandboxServiceClient().getFiles(sandboxInstanceId, paths); + if (!resp.success) { + this.logger().warn('readFiles failed', { error: resp.error }); + return { files: [] }; + } + return { files: resp.files.map(f => ({ path: f.filePath, content: f.fileContents })) }; + } + + async execCommands(commands: string[], timeout?: number) { + const { sandboxInstanceId } = this.state; + if (!sandboxInstanceId) { + return { success: false, results: [], error: 'No sandbox instance' } as any; + } + return await this.getSandboxServiceClient().executeCommands(sandboxInstanceId, commands, timeout); + } + + async regenerateFileByPath(path: string, issues: string[]): Promise<{ path: string; updatedPreview: string }> { + const { sandboxInstanceId } = this.state; + if (!sandboxInstanceId) { + throw new Error('No sandbox instance available'); + } + // Prefer local file manager; fallback to sandbox + let fileContents = ''; + let filePurpose = ''; + try { + const fmFile = this.fileManager.getFile(path); + if (fmFile) { + fileContents = fmFile.fileContents; + filePurpose = fmFile.filePurpose || ''; + } else { + const resp = await this.getSandboxServiceClient().getFiles(sandboxInstanceId, [path]); + const f = resp.success ? resp.files.find(f => f.filePath === path) : undefined; + if (!f) throw new Error(resp.error || `File not found: ${path}`); + fileContents = f.fileContents; + } + } catch (e) { + throw new Error(`Failed to read file for regeneration: ${String(e)}`); + } + + const regenerated = await this.regenerateFile({ filePath: path, fileContents, filePurpose }, issues, 0); + // Persist to sandbox instance + await this.getSandboxServiceClient().writeFiles(sandboxInstanceId, [{ filePath: regenerated.filePath, fileContents: regenerated.fileContents }], `Deep debugger fix: ${path}`); + return { path, updatedPreview: regenerated.fileContents.slice(0, 4000) }; + } + async waitForPreview(): Promise { this.logger().info("Waiting for preview"); if (!this.state.sandboxInstanceId) { @@ -1777,11 +1900,28 @@ export class SimpleCodeGeneratorAgent extends Agent { // because usually LLMs will only generate install commands or rm commands. // This is to handle the bug still present in a lot of apps because of an exponential growth of commands } - this.getSandboxServiceClient().executeCommands(sandboxInstanceId, cmds); this.broadcast(WebSocketMessageResponses.COMMAND_EXECUTING, { message: "Executing setup commands", commands: cmds, }); + try { + await Promise.race([ + this.getSandboxServiceClient().executeCommands(sandboxInstanceId, cmds), + new Promise((_, reject) => setTimeout(() => reject(new Error('Command execution timed out after 60 seconds')), 60000)) + ]); + this.broadcast(WebSocketMessageResponses.COMMAND_EXECUTED, { + message: "Setup commands executed successfully", + commands: cmds, + output: "Setup commands executed successfully", + }); + } catch (error) { + this.logger().error('Failed to execute commands', error); + this.broadcast(WebSocketMessageResponses.COMMAND_EXECUTION_FAILED, { + message: "Failed to execute setup commands", + commands: cmds, + error: String(error), + }); + } } // Clear any existing health check interval before creating a new one diff --git a/worker/agents/operations/UserConversationProcessor.ts b/worker/agents/operations/UserConversationProcessor.ts index 8e2e08a1..e00005aa 100644 --- a/worker/agents/operations/UserConversationProcessor.ts +++ b/worker/agents/operations/UserConversationProcessor.ts @@ -29,12 +29,12 @@ const COMPACTIFICATION_CONFIG = { CHARS_PER_TOKEN: 4, // Rough estimation: 1 token ≈ 4 characters } as const; -interface ToolCallStatusArgs { +export interface ToolCallStatusArgs { name: string; status: 'start' | 'success' | 'error'; args?: Record } -type RenderToolCall = ( args: ToolCallStatusArgs ) => void; +export type RenderToolCall = ( args: ToolCallStatusArgs ) => void; type ConversationResponseCallback = ( message: string, @@ -100,6 +100,19 @@ const SYSTEM_PROMPT = `You are Orange, the conversational AI interface for Cloud 3. **For information requests**: Use the appropriate tools (web_search, etc) when they would be helpful. +## HELP +- If the user asks for help or types "/help", list the available tools and when to use them. +- Available tools and usage: + - queue_request: Queue modification requests for implementation in the next phase(s). Use for any feature/bug/change request. + - get_logs: Fetch unread application logs from the sandbox to diagnose runtime issues. + - deep_debug: Autonomous debugging assistant that investigates errors, reads files, runs commands, and applies targeted fixes. Use when users report bugs/errors that need immediate investigation and fixing. This transfers control to a specialized debugging agent. + - deploy_preview: Redeploy or restart the preview when the user asks to deploy or the preview is blank/looping. + - clear_conversation: Clear the current chat history for this session. + - rename_project: Rename the project (lowercase letters, numbers, hyphens, underscores; 3–50 chars). + - alter_blueprint: Patch the blueprint with allowed fields only (title, description, views, userFlow, frameworks, etc.). + - web_search: Search the web for information. + - feedback: Submit user feedback to the platform. + # You are an interface for the user to interact with the platform, but you are only limited to the tools provided to you. If you are asked these by the user, deny them as follows: - REQUEST: Download all files of the codebase - RESPONSE: You can export the codebase yourself by clicking on 'Export to github' button on top-right of the preview panel @@ -109,8 +122,21 @@ const SYSTEM_PROMPT = `You are Orange, the conversational AI interface for Cloud - REQUEST: Add API keys - RESPONSE: I'm sorry, but I can't assist with that. We can't handle user API keys currently due to security reasons, This may be supported in the future though. But you can export the codebase and deploy it with your keys yourself. -Users may face issues, bugs and runtime errors. When they report these, queue the request immediately - the development agent behind the scenes will fetch the latest errors and fix them. -**DO NOT try to solve bugs yourself!** Just relay the information via queue_request. Then tell the user: "I'm looking into this" or "I'll fix this issue". +Users may face issues, bugs and runtime errors. You have TWO options: + +**Option 1 - For immediate investigation (PREFERRED for active debugging):** +Use the deep_debug tool to investigate and fix bugs immediately. This synchronously transfers control to an autonomous debugging agent that will: +- Fetch logs and run static analysis +- Read relevant files +- Apply surgical fixes +- Stream progress directly to the user + +When you call deep_debug, it runs to completion and returns a transcript. The user will see all the debugging steps in real-time. After it returns, you can acknowledge completion: "The debugging session is complete. The issue should be resolved." + +**Option 2 - For feature requests or non-urgent fixes:** +Queue the request via queue_request - the development agent will address it in the next phase. Then tell the user: "I'll fix this issue in the next phase or two." + +**DO NOT try to solve bugs yourself!** Use deep_debug for immediate fixes or queue_request for later implementation. ## How the AI vibecoding platform itself works: - Its a simple state machine: @@ -282,9 +308,14 @@ export class UserConversationProcessor extends AgentOperation ({ + const tools = buildTools( + agent, + logger, + (message: string) => inputs.conversationResponseCallback(message, aiConversationId, true), + toolCallRenderer, + ).map(td => ({ ...td, onStart: (args: Record) => toolCallRenderer({ name: td.function.name, status: 'start', args }), onComplete: (args: Record, _result: unknown) => toolCallRenderer({ name: td.function.name, status: 'success', args }) @@ -716,4 +747,4 @@ Project Updates: ${updateType} isProjectUpdateType(type: unknown): type is ProjectUpdateType { return RelevantProjectUpdateWebsoketMessages.includes(type as ProjectUpdateType); } -} \ No newline at end of file +} diff --git a/worker/agents/services/implementations/CodingAgent.ts b/worker/agents/services/implementations/CodingAgent.ts index 6959e36a..9bff8142 100644 --- a/worker/agents/services/implementations/CodingAgent.ts +++ b/worker/agents/services/implementations/CodingAgent.ts @@ -1,5 +1,8 @@ import { ProcessedImageAttachment } from "worker/types/image-attachment"; +import { Blueprint } from "worker/agents/schemas"; +import { ExecuteCommandsResponse, StaticAnalysisResponse } from "worker/services/sandbox/sandboxTypes"; import { ICodingAgent } from "../interfaces/ICodingAgent"; +import { OperationOptions } from "worker/agents/operations/common"; /* * CodingAgentInterface - stub for passing to tool calls @@ -35,4 +38,38 @@ export class CodingAgentInterface { queueRequest(request: string, images?: ProcessedImageAttachment[]): void { this.agentStub.queueUserRequest(request, images); } -} \ No newline at end of file + + clearConversation(): void { + this.agentStub.clearConversation(); + } + + getOperationOptions(): OperationOptions { + return this.agentStub.getOperationOptions(); + } + + updateProjectName(newName: string): Promise { + return this.agentStub.updateProjectName(newName); + } + + updateBlueprint(patch: Partial): Promise { + return this.agentStub.updateBlueprint(patch); + } + + // Generic debugging helpers — delegate to underlying agent + readFiles(paths: string[]): Promise<{ files: { path: string; content: string }[] }> { + return this.agentStub.readFiles(paths); + } + + runStaticAnalysisCode(files?: string[]): Promise { + return this.agentStub.runStaticAnalysisCode(files); + } + + execCommands(commands: string[], timeout?: number): Promise { + return this.agentStub.execCommands(commands, timeout); + } + + // Exposes a simplified regenerate API for tools + regenerateFile(path: string, issues: string[]): Promise<{ path: string; updatedPreview: string }> { + return this.agentStub.regenerateFileByPath(path, issues); + } +} diff --git a/worker/agents/services/interfaces/ICodingAgent.ts b/worker/agents/services/interfaces/ICodingAgent.ts index 617981b6..c523ec0b 100644 --- a/worker/agents/services/interfaces/ICodingAgent.ts +++ b/worker/agents/services/interfaces/ICodingAgent.ts @@ -1,7 +1,8 @@ -import { FileOutputType } from "worker/agents/schemas"; +import { FileOutputType, Blueprint } from "worker/agents/schemas"; import { BaseSandboxService } from "worker/services/sandbox/BaseSandboxService"; -import { PreviewType } from "worker/services/sandbox/sandboxTypes"; +import { ExecuteCommandsResponse, PreviewType, StaticAnalysisResponse } from "worker/services/sandbox/sandboxTypes"; import { ProcessedImageAttachment } from "worker/types/image-attachment"; +import { OperationOptions } from "worker/agents/operations/common"; export abstract class ICodingAgent { abstract getSandboxServiceClient(): BaseSandboxService; @@ -13,4 +14,20 @@ export abstract class ICodingAgent { abstract getLogs(reset?: boolean): Promise; abstract queueUserRequest(request: string, images?: ProcessedImageAttachment[]): void; + + abstract clearConversation(): void; + + abstract updateProjectName(newName: string): Promise; + + abstract updateBlueprint(patch: Partial): Promise; + + abstract getOperationOptions(): OperationOptions; + + abstract readFiles(paths: string[]): Promise<{ files: { path: string; content: string }[] }>; + + abstract runStaticAnalysisCode(files?: string[]): Promise; + + abstract execCommands(commands: string[], timeout?: number): Promise; + + abstract regenerateFileByPath(path: string, issues: string[]): Promise<{ path: string; updatedPreview: string }>; } diff --git a/worker/agents/tools/customTools.ts b/worker/agents/tools/customTools.ts index 98411179..929d6edb 100644 --- a/worker/agents/tools/customTools.ts +++ b/worker/agents/tools/customTools.ts @@ -1,11 +1,21 @@ import type { ToolDefinition } from './types'; import { StructuredLogger } from '../../logger'; +import { RenderToolCall } from '../operations/UserConversationProcessor'; import { toolWebSearchDefinition } from './toolkit/web-search'; import { toolFeedbackDefinition } from './toolkit/feedback'; import { createQueueRequestTool } from './toolkit/queue-request'; import { createGetLogsTool } from './toolkit/get-logs'; import { createDeployPreviewTool } from './toolkit/deploy-preview'; import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; +import { createDeepDebuggerTool } from "./toolkit/deep-debugger"; +import { createClearConversationTool } from './toolkit/clear-conversation'; +import { createRenameProjectTool } from './toolkit/rename-project'; +import { createAlterBlueprintTool } from './toolkit/alter-blueprint'; +import { DebugSession } from '../assistants/codeDebugger'; +import { createReadFilesTool } from './toolkit/read-files'; +import { createExecCommandsTool } from './toolkit/exec-commands'; +import { createRunAnalysisTool } from './toolkit/run-analysis'; +import { createRegenerateFileTool } from './toolkit/regenerate-file'; export async function executeToolWithDefinition( toolDef: ToolDefinition, @@ -23,7 +33,9 @@ export async function executeToolWithDefinition( */ export function buildTools( agent: CodingAgentInterface, - logger: StructuredLogger + logger: StructuredLogger, + streamCb?: (message: string) => void, + toolRenderer?: RenderToolCall, ): ToolDefinition[] { return [ toolWebSearchDefinition, @@ -31,5 +43,32 @@ export function buildTools( createQueueRequestTool(agent, logger), createGetLogsTool(agent, logger), createDeployPreviewTool(agent, logger), + createClearConversationTool(agent, logger), + createRenameProjectTool(agent, logger), + createAlterBlueprintTool(agent, logger), + // Deep autonomous debugging assistant tool + createDeepDebuggerTool(agent, logger, streamCb, toolRenderer), ]; } + +export function buildDebugTools(session: DebugSession, logger: StructuredLogger, toolRenderer?: RenderToolCall): ToolDefinition[] { + const tools = [ + createGetLogsTool(session.agent, logger), + createReadFilesTool(session.agent, logger), + createRunAnalysisTool(session.agent, logger), + createExecCommandsTool(session.agent, logger), + createRegenerateFileTool(session.agent, logger), + createDeployPreviewTool(session.agent, logger), + ]; + + // Attach tool renderer for UI visualization if provided + if (toolRenderer) { + return tools.map(td => ({ + ...td, + onStart: (args: Record) => toolRenderer({ name: td.function.name, status: 'start', args }), + onComplete: (args: Record, _result: unknown) => toolRenderer({ name: td.function.name, status: 'success', args }) + })); + } + + return tools; +} diff --git a/worker/agents/tools/toolkit/deep-debugger.ts b/worker/agents/tools/toolkit/deep-debugger.ts new file mode 100644 index 00000000..c50e95f5 --- /dev/null +++ b/worker/agents/tools/toolkit/deep-debugger.ts @@ -0,0 +1,71 @@ +import { ToolDefinition } from '../types'; +import { StructuredLogger } from '../../../logger'; +import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; +import { + DeepCodeDebugger, + type FileIndexEntry, +} from 'worker/agents/assistants/codeDebugger'; +import { RenderToolCall } from '../../operations/UserConversationProcessor'; + +export function createDeepDebuggerTool( + agent: CodingAgentInterface, + logger: StructuredLogger, + streamCb?: (message: string) => void, + toolRenderer?: RenderToolCall, +): ToolDefinition< + { issue: string; focus_paths?: string[] }, + { transcript: string } | { error: string } +> { + return { + type: 'function', + function: { + name: 'deep_debug', + description: + 'Autonomous deep debugging assistant. Investigates runtime errors and static analysis, reads targeted files (relative paths), runs commands (in project root, no cd), and applies surgical fixes via regenerate_file. Returns a concise transcript.', + parameters: { + type: 'object', + properties: { + issue: { type: 'string' }, + focus_paths: { type: 'array', items: { type: 'string' } }, + }, + required: ['issue'], + }, + }, + implementation: async ({ issue, focus_paths }: { issue: string; focus_paths?: string[] }) => { + try { + const operationOptions = agent.getOperationOptions(); + const filesIndex: FileIndexEntry[] = + operationOptions.context.allFiles + .map((f) => ({ + path: f.filePath, + purpose: f.filePurpose, + changes: + f.lastDiff || + (Array.isArray(f.unmerged) && f.unmerged.length + ? f.unmerged.join('\n') + : null), + })) + .filter( + (f) => + !focus_paths?.length || + focus_paths.some((p) => f.path.includes(p)), + ); + + const dbg = new DeepCodeDebugger( + operationOptions.env, + operationOptions.inferenceContext, + ); + const transcript = await dbg.run( + { issue }, + { filesIndex, agent }, + streamCb ? (chunk) => streamCb(chunk) : undefined, + toolRenderer, + ); + return { transcript }; + } catch (e) { + logger.error('Deep debugger failed', e); + return { error: `Deep debugger failed: ${String(e)}` }; + } + }, + }; +} diff --git a/worker/agents/tools/toolkit/deploy-preview.ts b/worker/agents/tools/toolkit/deploy-preview.ts index 9a6188a7..04c06dcd 100644 --- a/worker/agents/tools/toolkit/deploy-preview.ts +++ b/worker/agents/tools/toolkit/deploy-preview.ts @@ -15,7 +15,7 @@ export function createDeployPreviewTool( function: { name: 'deploy_preview', description: - 'Deploys the current application to a preview environment. **ONLY use this tool when:** (1) User explicitly requests deployment/deploy, OR (2) User reports the preview screen is blank/not showing anything, OR (3) User reports the preview page keeps refreshing/reloading. Do NOT use this tool for regular code changes - the preview auto-updates.', + 'Deploys the current application to a preview environment.', parameters: { type: 'object', properties: {}, diff --git a/worker/agents/tools/toolkit/exec-commands.ts b/worker/agents/tools/toolkit/exec-commands.ts new file mode 100644 index 00000000..013ba527 --- /dev/null +++ b/worker/agents/tools/toolkit/exec-commands.ts @@ -0,0 +1,49 @@ +import { ToolDefinition, ErrorResult } from '../types'; +import { StructuredLogger } from '../../../logger'; +import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; +import { ExecuteCommandsResponse } from 'worker/services/sandbox/sandboxTypes'; + +export type ExecCommandsArgs = { + commands: string[]; + timeout?: number; +}; + +export type ExecCommandsResult = ExecuteCommandsResponse | ErrorResult; + +export function createExecCommandsTool( + agent: CodingAgentInterface, + logger: StructuredLogger, +): ToolDefinition { + return { + type: 'function' as const, + function: { + name: 'exec_commands', + description: + 'Execute shell commands in the sandbox (e.g., tests, build).', + parameters: { + type: 'object', + properties: { + commands: { type: 'array', items: { type: 'string' } }, + timeout: { type: 'number' }, + }, + required: ['commands'], + }, + }, + implementation: async ({ commands, timeout }) => { + try { + logger.info('Executing commands', { + count: commands.length, + timeout, + }); + return await agent.execCommands(commands, timeout); + } catch (error) { + return { + error: + error instanceof Error + ? `Failed to execute commands: ${error.message}` + : 'Unknown error occurred while executing commands', + }; + } + }, + }; +} diff --git a/worker/agents/tools/toolkit/read-files.ts b/worker/agents/tools/toolkit/read-files.ts new file mode 100644 index 00000000..b37731c9 --- /dev/null +++ b/worker/agents/tools/toolkit/read-files.ts @@ -0,0 +1,45 @@ +import { ToolDefinition, ErrorResult } from '../types'; +import { StructuredLogger } from '../../../logger'; +import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; + +export type ReadFilesArgs = { + paths: string[]; +}; + +export type ReadFilesResult = + | { files: { path: string; content: string }[] } + | ErrorResult; + +export function createReadFilesTool( + agent: CodingAgentInterface, + logger: StructuredLogger, +): ToolDefinition { + return { + type: 'function' as const, + function: { + name: 'read_files', + description: + 'Read file contents by exact RELATIVE paths (sandbox pwd = project root). Prefer batching multiple paths in a single call to reduce overhead. Target all relevant files useful for understanding current context', + parameters: { + type: 'object', + properties: { + paths: { type: 'array', items: { type: 'string' } }, + }, + required: ['paths'], + }, + }, + implementation: async ({ paths }) => { + try { + logger.info('Reading files', { count: paths.length }); + return await agent.readFiles(paths); + } catch (error) { + return { + error: + error instanceof Error + ? `Failed to read files: ${error.message}` + : 'Unknown error occurred while reading files', + }; + } + }, + }; +} diff --git a/worker/agents/tools/toolkit/regenerate-file.ts b/worker/agents/tools/toolkit/regenerate-file.ts new file mode 100644 index 00000000..b9f01888 --- /dev/null +++ b/worker/agents/tools/toolkit/regenerate-file.ts @@ -0,0 +1,50 @@ +import { ToolDefinition, ErrorResult } from '../types'; +import { StructuredLogger } from '../../../logger'; +import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; + +export type RegenerateFileArgs = { + path: string; + issues: string[]; +}; + +export type RegenerateFileResult = + | { path: string; updatedPreview: string } + | ErrorResult; + +export function createRegenerateFileTool( + agent: CodingAgentInterface, + logger: StructuredLogger, +): ToolDefinition { + return { + type: 'function' as const, + function: { + name: 'regenerate_file', + description: + 'Apply a surgical fix to a file (search/replace style) using internal regeneration operation, then persist changes.', + parameters: { + type: 'object', + properties: { + path: { type: 'string' }, + issues: { type: 'array', items: { type: 'string' } }, + }, + required: ['path', 'issues'], + }, + }, + implementation: async ({ path, issues }) => { + try { + logger.info('Regenerating file', { + path, + issuesCount: issues.length, + }); + return await agent.regenerateFile(path, issues); + } catch (error) { + return { + error: + error instanceof Error + ? `Failed to regenerate file: ${error.message}` + : 'Unknown error occurred while regenerating file', + }; + } + }, + }; +} diff --git a/worker/agents/tools/toolkit/run-analysis.ts b/worker/agents/tools/toolkit/run-analysis.ts new file mode 100644 index 00000000..52e67c78 --- /dev/null +++ b/worker/agents/tools/toolkit/run-analysis.ts @@ -0,0 +1,37 @@ +import { ToolDefinition } from '../types'; +import { StructuredLogger } from '../../../logger'; +import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; +import { StaticAnalysisResponse } from 'worker/services/sandbox/sandboxTypes'; + +export type RunAnalysisArgs = { + files?: string[]; +}; + +export type RunAnalysisResult = StaticAnalysisResponse; + +export function createRunAnalysisTool( + agent: CodingAgentInterface, + logger: StructuredLogger, +): ToolDefinition { + return { + type: 'function' as const, + function: { + name: 'run_analysis', + description: + 'Run static analysis (lint + typecheck), optionally scoped to given files.', + parameters: { + type: 'object', + properties: { + files: { type: 'array', items: { type: 'string' } }, + }, + required: [], + }, + }, + implementation: async ({ files }) => { + logger.info('Running static analysis', { + filesCount: files?.length || 0, + }); + return await agent.runStaticAnalysisCode(files); + }, + }; +} diff --git a/worker/api/websocketTypes.ts b/worker/api/websocketTypes.ts index 228c7486..1a92294e 100644 --- a/worker/api/websocketTypes.ts +++ b/worker/api/websocketTypes.ts @@ -92,6 +92,20 @@ type CommandExecutingMessage = { commands: string[]; }; +type CommandExecutedMessage = { + type: 'command_executed'; + message: string; + commands: string[]; + output?: string; +}; + +type CommandExecutionFailedMessage = { + type: 'command_execution_failed'; + message: string; + commands: string[]; + error?: string; +}; + type CodeReviewingMessage = { type: 'code_reviewing'; message: string; @@ -404,6 +418,8 @@ export type WebSocketMessage = | CodeReviewingMessage | CodeReviewedMessage | CommandExecutingMessage + | CommandExecutedMessage + | CommandExecutionFailedMessage | RuntimeErrorFoundMessage | CodeFixEdits | StaticAnalysisResults From 9ae464050cad6a55aa10f87df01f0739ccc00364 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 16 Oct 2025 22:26:38 -0400 Subject: [PATCH 010/150] feat: implement collapsible tool result viewer with JSON formatting support --- src/routes/chat/components/messages.tsx | 172 +++++++++++++++--- src/routes/chat/components/phase-timeline.tsx | 4 +- .../chat/utils/handle-websocket-message.ts | 112 +++++++++--- src/routes/chat/utils/message-helpers.ts | 102 +++++++++-- worker/agents/assistants/codeDebugger.ts | 4 +- worker/agents/core/websocket.ts | 1 + worker/agents/inferutils/config.ts | 7 + worker/agents/inferutils/config.types.ts | 1 + worker/agents/inferutils/core.ts | 2 +- .../operations/UserConversationProcessor.ts | 3 +- 10 files changed, 334 insertions(+), 74 deletions(-) diff --git a/src/routes/chat/components/messages.tsx b/src/routes/chat/components/messages.tsx index f88f9cf2..1c7965cc 100644 --- a/src/routes/chat/components/messages.tsx +++ b/src/routes/chat/components/messages.tsx @@ -3,8 +3,9 @@ import clsx from 'clsx'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import rehypeExternalLinks from 'rehype-external-links'; -import { LoaderCircle, Check, AlertTriangle } from 'lucide-react'; +import { LoaderCircle, Check, AlertTriangle, ChevronDown, ChevronRight } from 'lucide-react'; import type { ToolEvent } from '../utils/message-helpers'; +import { useState } from 'react'; /** * Strip internal system tags that should not be displayed to users @@ -32,10 +33,119 @@ export function UserMessage({ message }: { message: string }) { ); } +type ContentItem = + | { type: 'text'; content: string; key: string } + | { type: 'tool'; event: ToolEvent; key: string }; + +function JsonRenderer({ data }: { data: unknown }) { + if (typeof data !== 'object' || data === null) { + return {String(data)}; + } + + return ( +
+ {Object.entries(data).map(([key, value]) => ( +
+ {key}: + {typeof value === 'object' && value !== null ? ( +
+ +
+ ) : ( + + {String(value)} + + )} +
+ ))} +
+ ); +} + +function ToolResultRenderer({ result }: { result: string }) { + try { + const parsed = JSON.parse(result); + return ; + } catch { + return
{result}
; + } +} + +function ToolStatusIndicator({ event }: { event: ToolEvent }) { + const [isExpanded, setIsExpanded] = useState(false); + const hasResult = event.status === 'success' && event.result; + + const statusText = event.status === 'start' ? 'Running' : + event.status === 'success' ? 'Completed' : + 'Error'; + + const StatusIcon = event.status === 'start' ? LoaderCircle : + event.status === 'success' ? Check : + AlertTriangle; + + const iconClass = event.status === 'start' ? 'size-3 animate-spin' : 'size-3'; + + return ( +
+ + + {isExpanded && hasResult && event.result && ( +
+ +
+ )} +
+ ); +} + +function buildOrderedContent(message: string, inlineToolEvents: ToolEvent[]): ContentItem[] { + if (!inlineToolEvents.length) { + return message ? [{ type: 'text', content: message, key: 'content-0' }] : []; + } + + const items: ContentItem[] = []; + let lastPos = 0; + + for (const event of inlineToolEvents) { + const pos = event.contentLength ?? 0; + + // Add text before this event + if (pos > lastPos && message.slice(lastPos, pos)) { + items.push({ type: 'text', content: message.slice(lastPos, pos), key: `text-${lastPos}` }); + } + + // Add event + items.push({ type: 'tool', event, key: `tool-${event.timestamp}` }); + lastPos = pos; + } + + // Add remaining text + if (lastPos < message.length && message.slice(lastPos)) { + items.push({ type: 'text', content: message.slice(lastPos), key: `text-${lastPos}` }); + } + + return items; +} + export function AIMessage({ message, isThinking, - toolEvents, + toolEvents = [], }: { message: string; isThinking?: boolean; @@ -43,6 +153,18 @@ export function AIMessage({ }) { const sanitizedMessage = sanitizeMessageForDisplay(message); + // Separate: events without contentLength = top (restored), with contentLength = inline (streaming) + const topToolEvents = toolEvents.filter(ev => ev.contentLength === undefined); + const inlineToolEvents = toolEvents.filter(ev => ev.contentLength !== undefined) + .sort((a, b) => (a.contentLength ?? 0) - (b.contentLength ?? 0)); + + const orderedContent = buildOrderedContent(sanitizedMessage, inlineToolEvents); + + // Don't render if completely empty + if (!sanitizedMessage && !topToolEvents.length && !orderedContent.length) { + return null; + } + return (
@@ -50,32 +172,32 @@ export function AIMessage({
Orange
- {toolEvents && toolEvents.length > 0 && ( -
- {toolEvents.map((ev) => ( -
- {ev.status === 'start' && ( - - )} - {ev.status === 'success' && } - {ev.status === 'error' && } - - {ev.status === 'start' && 'Running'} - {ev.status === 'success' && 'Completed'} - {ev.status === 'error' && 'Error'} - {' '} - {ev.name} - -
+ + {/* Message content with inline tool events (from streaming) */} + {orderedContent.length > 0 && ( +
+ {orderedContent.map((item) => ( + item.type === 'text' ? ( + + {item.content} + + ) : ( +
+ +
+ ) + ))} +
+ )} + + {/* Completed tools (from restoration) - shown at end */} + {topToolEvents.length > 0 && ( +
+ {topToolEvents.map((ev) => ( + ))}
)} - - {sanitizedMessage} -
); diff --git a/src/routes/chat/components/phase-timeline.tsx b/src/routes/chat/components/phase-timeline.tsx index afc0f318..4b5bda52 100644 --- a/src/routes/chat/components/phase-timeline.tsx +++ b/src/routes/chat/components/phase-timeline.tsx @@ -329,7 +329,7 @@ export function PhaseTimeline({ if (validatingPhase) { return { text: `Reviewing: ${truncatePhaseName(validatingPhase.name)}`, - subtitle: 'Running tests and fixing issues...', + subtitle: 'Identifying issues...', icon: , badge: phaseBadge }; @@ -818,7 +818,7 @@ export function PhaseTimeline({ Reviewing phase...
- Running tests and fixing any issues + Identifying issues...
); } else if (isPreviewDeploying) { diff --git a/src/routes/chat/utils/handle-websocket-message.ts b/src/routes/chat/utils/handle-websocket-message.ts index b29fb5b8..5e6f7010 100644 --- a/src/routes/chat/utils/handle-websocket-message.ts +++ b/src/routes/chat/utils/handle-websocket-message.ts @@ -78,6 +78,9 @@ export interface HandleMessageDeps { } export function createWebSocketMessageHandler(deps: HandleMessageDeps) { + // Track review lifecycle within this handler instance + let lastReviewIssueCount = 0; + let reviewStartAnnounced = false; const extractTextContent = (content: ConversationMessage['content']): string => { if (!content) return ''; if (typeof content === 'string') return content; @@ -252,34 +255,67 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { const history: ReadonlyArray = state?.runningHistory ?? []; logger.debug('Received conversation_state with messages:', history.length); - const restoredMessages: ChatMessage[] = history.reduce((acc, msg) => { - if (msg.role !== 'user' && msg.role !== 'assistant') return acc; + const restoredMessages: ChatMessage[] = []; + let currentAssistant: ChatMessage | null = null; + + const ensureToolEvents = (assistant: ChatMessage) => { + if (!assistant.ui) assistant.ui = { toolEvents: [] }; + if (!assistant.ui.toolEvents) assistant.ui.toolEvents = []; + }; + + for (const msg of history) { const text = extractTextContent(msg.content); - if (!text || text.includes('')) return acc; - - const convId = msg.conversationId; - const isArchive = msg.role === 'assistant' && convId.startsWith('archive-'); - - acc.push({ - role: msg.role, - conversationId: convId, - content: isArchive ? 'previous history was compacted' : text, - }); - return acc; - }, []); + if (text?.includes('')) continue; + + if (msg.role === 'user') { + restoredMessages.push({ + role: 'user', + conversationId: msg.conversationId, + content: text || '', + }); + currentAssistant = null; + } else if (msg.role === 'assistant') { + const content = msg.conversationId.startsWith('archive-') + ? 'previous history was compacted' + : (text || ''); + + if (currentAssistant) { + if (content) { + currentAssistant.content += (currentAssistant.content ? '\n\n' : '') + content; + } + if (msg.tool_calls?.length) ensureToolEvents(currentAssistant); + } else { + currentAssistant = { + role: 'assistant', + conversationId: msg.conversationId, + content, + ui: msg.tool_calls?.length ? { toolEvents: [] } : undefined, + }; + restoredMessages.push(currentAssistant); + } + } else if (msg.role === 'tool' && 'name' in msg && msg.name && currentAssistant) { + ensureToolEvents(currentAssistant); + currentAssistant.ui!.toolEvents!.push({ + name: msg.name, + status: 'success', + timestamp: Date.now(), + result: text || undefined, + contentLength: currentAssistant.content.length, + }); + } + } if (restoredMessages.length > 0) { - logger.debug('Merging conversation_state history with existing messages (preserving fetch indicator):', restoredMessages.length); + logger.debug('Merging conversation_state with', restoredMessages.length, 'messages'); setMessages(prev => { const hasFetching = prev.some(m => m.role === 'assistant' && m.conversationId === 'fetching-chat'); - let next = prev; if (hasFetching) { - // Mark fetching tool-event as completed - next = appendToolEvent(next, 'fetching-chat', { name: 'fetching your latest conversations', status: 'success' }); - // Append restored messages after the fetch indicator + const next = appendToolEvent(prev, 'fetching-chat', { + name: 'fetching your latest conversations', + status: 'success' + }); return [...next, ...restoredMessages]; } - // Fallback: replace if no fetching indicator exists return restoredMessages; }); } @@ -322,12 +358,17 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { case 'file_regenerating': { setFiles((prev) => setFileGenerating(prev, message.filePath, 'File being regenerated...')); setPhaseTimeline((prev) => updatePhaseFileStatus(prev, message.filePath, 'generating')); + // Activates fixing stage only when actual regenerations begin + updateStage('fix', { status: 'active', metadata: lastReviewIssueCount > 0 ? `Fixing ${lastReviewIssueCount} issues` : 'Fixing issues' }); break; } case 'generation_started': { updateStage('code', { status: 'active' }); setTotalFiles(message.totalFiles); + // Reset review tracking for a new generation run + lastReviewIssueCount = 0; + reviewStartAnnounced = false; break; } @@ -335,6 +376,8 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { setIsRedeployReady(true); setFiles((prev) => setAllFilesCompleted(prev)); setProjectStages((prev) => completeStages(prev, ['code', 'validate', 'fix'])); + // Ensure fix stage metadata is cleared on final completion + updateStage('fix', { status: 'completed', metadata: undefined }); sendMessage(createAIMessage('generation-complete', 'Code generation has been completed.')); setIsPhaseProgressActive(false); @@ -360,16 +403,20 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { case 'code_reviewed': { const reviewData = message.review; - const totalIssues = reviewData?.filesToFix?.reduce((count: number, file: any) => + const totalIssues = reviewData?.filesToFix?.reduce((count: number, file: any) => count + file.issues.length, 0) || 0; - + + lastReviewIssueCount = totalIssues; + let reviewMessage = 'Code review complete'; if (reviewData?.issuesFound) { reviewMessage = `Code review complete - ${totalIssues} issue${totalIssues !== 1 ? 's' : ''} found across ${reviewData.filesToFix?.length || 0} file${reviewData.filesToFix?.length !== 1 ? 's' : ''}`; } else { reviewMessage = 'Code review complete - no issues found'; } - + + // Mark validation as completed at the end of review + updateStage('validate', { status: 'completed' }); sendMessage(createAIMessage('code_reviewed', reviewMessage)); break; } @@ -391,19 +438,28 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { (message.staticAnalysis?.typecheck?.issues?.length || 0) + (message.runtimeErrors.length || 0); + lastReviewIssueCount = totalIssues; + + // Announce review start once, right after main code gen + if (!reviewStartAnnounced) { + sendMessage(createAIMessage('review_start', 'App generation complete, now reviewing code indepth')); + reviewStartAnnounced = true; + } + + // Only show reviewing as active; do not activate fix until regeneration actually starts updateStage('validate', { status: 'active' }); + // Show identified issues count while review runs, but keep fix stage pending + updateStage('fix', { status: 'pending', metadata: totalIssues > 0 ? `Identified ${totalIssues} issues` : undefined }); if (totalIssues > 0) { - updateStage('fix', { status: 'active', metadata: `Fixing ${totalIssues} issues` }); - const errorDetails = [ `Lint Issues: ${JSON.stringify(message.staticAnalysis?.lint?.issues)}`, `Type Errors: ${JSON.stringify(message.staticAnalysis?.typecheck?.issues)}`, `Runtime Errors: ${JSON.stringify(message.runtimeErrors)}`, `Client Errors: ${JSON.stringify(message.clientErrors)}`, ].filter(Boolean).join('\n'); - - onDebugMessage?.('warning', + + onDebugMessage?.('warning', `Generation Issues Found (${totalIssues} total)`, errorDetails, 'Code Generation' @@ -414,7 +470,7 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { case 'phase_generating': { updateStage('validate', { status: 'completed' }); - updateStage('fix', { status: 'completed' }); + updateStage('fix', { status: 'completed', metadata: undefined }); sendMessage(createAIMessage('phase_generating', message.message)); setIsThinking(true); setIsPhaseProgressActive(true); diff --git a/src/routes/chat/utils/message-helpers.ts b/src/routes/chat/utils/message-helpers.ts index 2c7d4a35..a4816f4f 100644 --- a/src/routes/chat/utils/message-helpers.ts +++ b/src/routes/chat/utils/message-helpers.ts @@ -6,6 +6,8 @@ export type ToolEvent = { name: string; status: 'start' | 'success' | 'error'; timestamp: number; + contentLength?: number; // Position in content when event was added (for inline rendering) + result?: string; // Tool execution result (for completed tools) }; export type ChatMessage = Omit & { @@ -145,9 +147,10 @@ export function handleStreamingMessage( } /** - * Append or update a tool event inline within an AI message bubble - * - If a message with messageId doesn't exist yet, create a placeholder AI message with empty content - * - If a matching 'start' exists and a 'success' comes in for the same tool, update that entry in place + * Append or update a tool event + * - Tool 'start': Add with current position for inline rendering + * - Tool 'success': Update matching 'start' to 'success' in place OR add new success event if content changed + * - Tool 'error': Add error event with position for inline rendering */ export function appendToolEvent( messages: ChatMessage[], @@ -163,33 +166,104 @@ export function appendToolEvent( role: 'assistant', conversationId, content: '', - ui: { toolEvents: [{ name: tool.name, status: tool.status, timestamp }] }, + ui: { + toolEvents: [{ + name: tool.name, + status: tool.status, + timestamp, + contentLength: 0 + }] + }, }; return [...messages, newMsg]; } return messages.map((m, i) => { if (i !== idx) return m; + const current = m.ui?.toolEvents ?? []; + const currentContentLength = m.content.length; + + if (tool.status === 'start') { + // Add new tool start event with current position + return { + ...m, + ui: { + ...m.ui, + toolEvents: [...current, { + name: tool.name, + status: 'start', + timestamp, + contentLength: currentContentLength + }] + } + }; + } + if (tool.status === 'success') { - // Find last 'start' for this tool and flip it to success - for (let j = current.length - 1; j >= 0; j--) { - if (current[j].name === tool.name) { + // Find the matching 'start' event + const startEventIndex = current.findIndex(ev => ev.name === tool.name && ev.status === 'start'); + + if (startEventIndex !== -1) { + const startEvent = current[startEventIndex]; + + // If no content after start, update in place + if (startEvent.contentLength === currentContentLength) { return { ...m, ui: { ...m.ui, - toolEvents: current.map((ev, k) => - k === j ? { ...ev, status: 'success', timestamp } : ev - ), + toolEvents: current.map((ev, j) => + j === startEventIndex + ? { name: ev.name, status: 'success' as const, timestamp, contentLength: currentContentLength } + : ev + ) } }; } + + // Content changed, add new success event at current position + return { + ...m, + ui: { + ...m.ui, + toolEvents: [...current, { + name: tool.name, + status: 'success', + timestamp, + contentLength: currentContentLength + }] + } + }; } - // If no prior start, just append success as a separate line - return { ...m, ui: { ...m.ui, toolEvents: [...current, { name: tool.name, status: 'success', timestamp }] } }; + + // No prior start found, just add success event with position + return { + ...m, + ui: { + ...m.ui, + toolEvents: [...current, { + name: tool.name, + status: 'success', + timestamp, + contentLength: currentContentLength + }] + } + }; } - // Default: append event - return { ...m, ui: { ...m.ui, toolEvents: [...current, { name: tool.name, status: tool.status, timestamp }] } }; + + // Error status - add with position for inline rendering + return { + ...m, + ui: { + ...m.ui, + toolEvents: [...current, { + name: tool.name, + status: 'error', + timestamp, + contentLength: currentContentLength + }] + } + }; }); } diff --git a/worker/agents/assistants/codeDebugger.ts b/worker/agents/assistants/codeDebugger.ts index 011650c6..3ad71991 100644 --- a/worker/agents/assistants/codeDebugger.ts +++ b/worker/agents/assistants/codeDebugger.ts @@ -118,8 +118,8 @@ export class DeepCodeDebugger extends Assistant { const result = await executeInference({ env: this.env, context: this.inferenceContext, - agentActionName: 'conversationalResponse', - modelConfig: this.modelConfigOverride || AGENT_CONFIG.codeReview, + agentActionName: 'deepDebugger', + modelConfig: this.modelConfigOverride || AGENT_CONFIG.deepDebugger, messages, tools, stream: streamCb diff --git a/worker/agents/core/websocket.ts b/worker/agents/core/websocket.ts index e58f5191..7a7e926e 100644 --- a/worker/agents/core/websocket.ts +++ b/worker/agents/core/websocket.ts @@ -218,6 +218,7 @@ export function handleWebSocketMessage(agent: SimpleCodeGeneratorAgent, connecti case WebSocketMessageRequests.GET_CONVERSATION_STATE: try { const state = agent.getConversationState(); + logger.info('Conversation state retrieved', state); sendToConnection(connection, WebSocketMessageResponses.CONVERSATION_STATE, { state }); } catch (error) { logger.error('Error fetching conversation state:', error); diff --git a/worker/agents/inferutils/config.ts b/worker/agents/inferutils/config.ts index f2f5aaf6..552233db 100644 --- a/worker/agents/inferutils/config.ts +++ b/worker/agents/inferutils/config.ts @@ -131,6 +131,13 @@ export const AGENT_CONFIG: AgentConfig = { temperature: 0, fallbackModel: AIModels.GEMINI_2_5_PRO, }, + deepDebugger: { + name: AIModels.GEMINI_2_5_PRO, + reasoning_effort: 'high', + max_tokens: 32000, + temperature: 0.1, + fallbackModel: AIModels.GEMINI_2_5_FLASH, + }, codeReview: { name: AIModels.GEMINI_2_5_PRO, reasoning_effort: 'medium', diff --git a/worker/agents/inferutils/config.types.ts b/worker/agents/inferutils/config.types.ts index 59b708dd..a943c490 100644 --- a/worker/agents/inferutils/config.types.ts +++ b/worker/agents/inferutils/config.types.ts @@ -66,6 +66,7 @@ export interface AgentConfig { realtimeCodeFixer: ModelConfig; fastCodeFixer: ModelConfig; conversationalResponse: ModelConfig; + deepDebugger: ModelConfig; } // Provider and reasoning effort types for validation diff --git a/worker/agents/inferutils/core.ts b/worker/agents/inferutils/core.ts index 8a24ad1c..b849f0d2 100644 --- a/worker/agents/inferutils/core.ts +++ b/worker/agents/inferutils/core.ts @@ -440,7 +440,7 @@ export async function infer({ // Check tool calling depth to prevent infinite recursion const currentDepth = toolCallContext?.depth ?? 0; - if (currentDepth >= MAX_TOOL_CALLING_DEPTH) { + if (currentDepth >= MAX_TOOL_CALLING_DEPTH && actionKey !== 'deepDebugger') { console.warn(`Tool calling depth limit reached (${currentDepth}/${MAX_TOOL_CALLING_DEPTH}). Stopping recursion.`); // Return a response indicating max depth reached if (schema) { diff --git a/worker/agents/operations/UserConversationProcessor.ts b/worker/agents/operations/UserConversationProcessor.ts index e00005aa..90604a81 100644 --- a/worker/agents/operations/UserConversationProcessor.ts +++ b/worker/agents/operations/UserConversationProcessor.ts @@ -108,7 +108,7 @@ const SYSTEM_PROMPT = `You are Orange, the conversational AI interface for Cloud - deep_debug: Autonomous debugging assistant that investigates errors, reads files, runs commands, and applies targeted fixes. Use when users report bugs/errors that need immediate investigation and fixing. This transfers control to a specialized debugging agent. - deploy_preview: Redeploy or restart the preview when the user asks to deploy or the preview is blank/looping. - clear_conversation: Clear the current chat history for this session. - - rename_project: Rename the project (lowercase letters, numbers, hyphens, underscores; 3–50 chars). + - rename_project: Rename the project (lowercase letters, numbers, hyphens, underscores; 3-50 chars). - alter_blueprint: Patch the blueprint with allowed fields only (title, description, views, userFlow, frameworks, etc.). - web_search: Search the web for information. - feedback: Submit user feedback to the platform. @@ -388,7 +388,6 @@ export class UserConversationProcessor extends AgentOperation 0) { messages.push( ...result.toolCallContext.messages - .filter((message) => !(message.role === 'assistant' && typeof(message.content) === 'string' && message.content.includes('Internal Memo'))) .map((message) => ({ ...message, conversationId: IdGenerator.generateConversationId() })) ); } From 95ed92f0111bab5f78318a810c9c965afc38a388 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 16 Oct 2025 23:32:33 -0400 Subject: [PATCH 011/150] feat: add deep debugger transcript UI with collapsible tool results --- src/routes/chat/components/messages.tsx | 141 ++++++++++++++++--- worker/agents/assistants/codeDebugger.ts | 13 +- worker/agents/tools/toolkit/deep-debugger.ts | 6 +- 3 files changed, 139 insertions(+), 21 deletions(-) diff --git a/src/routes/chat/components/messages.tsx b/src/routes/chat/components/messages.tsx index 1c7965cc..1082a94c 100644 --- a/src/routes/chat/components/messages.tsx +++ b/src/routes/chat/components/messages.tsx @@ -3,8 +3,9 @@ import clsx from 'clsx'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import rehypeExternalLinks from 'rehype-external-links'; -import { LoaderCircle, Check, AlertTriangle, ChevronDown, ChevronRight } from 'lucide-react'; +import { LoaderCircle, Check, AlertTriangle, ChevronDown, ChevronRight, MessageSquare } from 'lucide-react'; import type { ToolEvent } from '../utils/message-helpers'; +import type { ConversationMessage } from '@/api-types'; import { useState } from 'react'; /** @@ -62,9 +63,118 @@ function JsonRenderer({ data }: { data: unknown }) { ); } -function ToolResultRenderer({ result }: { result: string }) { +function extractTextContent(content: unknown): string { + if (typeof content === 'string') return content; + if (Array.isArray(content)) { + return content + .map(item => item.type === 'text' ? item.text : '') + .join(''); + } + return ''; +} + +function convertToToolEvent(msg: ConversationMessage, idx: number): ToolEvent | null { + if (msg.role !== 'tool' || !('name' in msg) || !msg.name) return null; + + return { + name: msg.name, + status: 'success', + timestamp: Date.now() + idx, + result: extractTextContent(msg.content), + }; +} + +function MessageContentRenderer({ + content, + toolEvents = [] +}: { + content: string; + toolEvents?: ToolEvent[]; +}) { + const inlineToolEvents = toolEvents.filter(ev => ev.contentLength !== undefined) + .sort((a, b) => (a.contentLength ?? 0) - (b.contentLength ?? 0)); + + const orderedContent = buildOrderedContent(content, inlineToolEvents); + + if (orderedContent.length === 0) return null; + + return ( +
+ {orderedContent.map((item) => ( + item.type === 'text' ? ( + + {item.content} + + ) : ( +
+ +
+ ) + ))} +
+ ); +} + +function DeepDebugTranscript({ transcript }: { transcript: ConversationMessage[] }) { + // Build map of tool results by tool_call_id for matching + const toolResultsMap = new Map(); + transcript.forEach((msg, idx) => { + if (msg.role === 'tool' && 'tool_call_id' in msg) { + const toolCallId = (msg as any).tool_call_id; + if (toolCallId && typeof toolCallId === 'string') { + const toolEvent = convertToToolEvent(msg, idx); + if (toolEvent) toolResultsMap.set(toolCallId, toolEvent); + } + } + }); + + return ( +
+
+ + Deep Debugger Transcript +
+ {transcript.map((msg, idx) => { + if (msg.role === 'tool') return null; // Tool results rendered with assistant messages + + const text = extractTextContent(msg.content); + if (!text) return null; + + if (msg.role === 'assistant') { + // Match tool_calls with their results + const toolEvents: ToolEvent[] = msg.tool_calls?.map(tc => { + const funcName = 'function' in tc ? tc.function.name : 'unknown_tool'; + const matchedResult = toolResultsMap.get(tc.id); + return matchedResult || { + name: funcName, + status: 'start' as const, + timestamp: Date.now() + idx, + contentLength: 0, + }; + }) || []; + + return ( +
+ +
+ ); + } + + return null; + })} +
+ ); +} + +function ToolResultRenderer({ result, toolName }: { result: string; toolName: string }) { try { const parsed = JSON.parse(result); + + // Special handling for deep_debug transcript + if (toolName === 'deep_debug' && Array.isArray(parsed.transcript)) { + return ; + } + return ; } catch { return
{result}
; @@ -74,6 +184,7 @@ function ToolResultRenderer({ result }: { result: string }) { function ToolStatusIndicator({ event }: { event: ToolEvent }) { const [isExpanded, setIsExpanded] = useState(false); const hasResult = event.status === 'success' && event.result; + const isDeepDebug = event.name === 'deep_debug'; const statusText = event.status === 'start' ? 'Running' : event.status === 'success' ? 'Completed' : @@ -90,7 +201,8 @@ function ToolStatusIndicator({ event }: { event: ToolEvent }) { {isExpanded && hasResult && event.result && ( -
- +
+
)}
@@ -175,18 +292,8 @@ export function AIMessage({ {/* Message content with inline tool events (from streaming) */} {orderedContent.length > 0 && ( -
- {orderedContent.map((item) => ( - item.type === 'text' ? ( - - {item.content} - - ) : ( -
- -
- ) - ))} +
+
)} diff --git a/worker/agents/assistants/codeDebugger.ts b/worker/agents/assistants/codeDebugger.ts index 3ad71991..da2faa60 100644 --- a/worker/agents/assistants/codeDebugger.ts +++ b/worker/agents/assistants/codeDebugger.ts @@ -1,5 +1,6 @@ import Assistant from './assistant'; import { + ConversationMessage, createAssistantMessage, createSystemMessage, createUserMessage, @@ -13,6 +14,7 @@ import { CodingAgentInterface } from '../services/implementations/CodingAgent'; import { AGENT_CONFIG } from '../inferutils/config'; import { buildDebugTools } from '../tools/customTools'; import { RenderToolCall } from '../operations/UserConversationProcessor'; +import { IdGenerator } from '../utils/idGenerator'; const SYSTEM_PROMPT = `You are an autonomous code debugging assistant. Goal: find root-cause fast and apply minimal, surgical fixes. @@ -125,12 +127,19 @@ export class DeepCodeDebugger extends Assistant { stream: streamCb ? { chunk_size: 64, onChunk: (c) => streamCb(c) } : undefined, - temperature: 0.0, - reasoning_effort: 'low', + temperature: 0.2, + // reasoning_effort: 'low', }); const out = result?.string || ''; this.save([createAssistantMessage(out)]); return out; } + + getTranscript(): ConversationMessage[] { + return this.getHistory().map((m) => ({ + ...m, + conversationId: IdGenerator.generateConversationId(), + })); + } } diff --git a/worker/agents/tools/toolkit/deep-debugger.ts b/worker/agents/tools/toolkit/deep-debugger.ts index c50e95f5..14fd0663 100644 --- a/worker/agents/tools/toolkit/deep-debugger.ts +++ b/worker/agents/tools/toolkit/deep-debugger.ts @@ -6,6 +6,7 @@ import { type FileIndexEntry, } from 'worker/agents/assistants/codeDebugger'; import { RenderToolCall } from '../../operations/UserConversationProcessor'; +import { ConversationMessage } from '../../inferutils/common'; export function createDeepDebuggerTool( agent: CodingAgentInterface, @@ -14,7 +15,7 @@ export function createDeepDebuggerTool( toolRenderer?: RenderToolCall, ): ToolDefinition< { issue: string; focus_paths?: string[] }, - { transcript: string } | { error: string } + { transcript: ConversationMessage[] } | { error: string } > { return { type: 'function', @@ -55,12 +56,13 @@ export function createDeepDebuggerTool( operationOptions.env, operationOptions.inferenceContext, ); - const transcript = await dbg.run( + await dbg.run( { issue }, { filesIndex, agent }, streamCb ? (chunk) => streamCb(chunk) : undefined, toolRenderer, ); + const transcript = dbg.getTranscript(); return { transcript }; } catch (e) { logger.error('Deep debugger failed', e); From 4579da2530a45d91294fd108d9937254d93e68de Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 15:35:48 -0400 Subject: [PATCH 012/150] feat: add duration filter option for log retrieval in sandbox --- container/cli-tools.ts | 45 ++++++++++++++++++- worker/services/sandbox/BaseSandboxService.ts | 2 +- .../services/sandbox/remoteSandboxService.ts | 8 +++- worker/services/sandbox/sandboxSdkClient.ts | 7 +-- 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/container/cli-tools.ts b/container/cli-tools.ts index d860f92c..9eb35abb 100755 --- a/container/cli-tools.ts +++ b/container/cli-tools.ts @@ -741,6 +741,7 @@ class LogCommands { instanceId: string; format?: 'json' | 'raw'; reset?: boolean; + durationSeconds?: number; }): Promise { try { const { promises: fs } = require('fs'); @@ -827,6 +828,11 @@ class LogCommands { await releaseLock(); } + // Filter logs by duration if specified + if (options.durationSeconds && options.durationSeconds > 0) { + logs = LogCommands.filterLogsByDuration(logs, options.durationSeconds); + } + if (options.format === 'raw') { console.log(logs); } else { @@ -850,6 +856,39 @@ class LogCommands { } } + /** + * Filter logs by duration (keep only logs newer than X seconds ago) + * Log format: [2025-10-17T05:30:24.985Z] [stdout] content + */ + static filterLogsByDuration(logs: string, durationSeconds: number): string { + if (!logs || logs.trim().length === 0) { + return logs; + } + + const lines = logs.split('\n'); + const now = Date.now(); + const cutoffTime = now - (durationSeconds * 1000); + + const filteredLines = lines.filter(line => { + // Match log format: [ISO_TIMESTAMP] [stream] content + const timestampMatch = line.match(/^\[([^\]]+)\]/); + if (!timestampMatch) { + // If no timestamp, keep the line (might be continuation of previous log) + return true; + } + + try { + const timestamp = new Date(timestampMatch[1]).getTime(); + return timestamp >= cutoffTime; + } catch (error) { + // If timestamp parsing fails, keep the line + return true; + } + }); + + return filteredLines.join('\n'); + } + static async stats(options: { instanceId: string; dbPath?: string }): Promise { const storage = new StorageManager(undefined, options.dbPath); @@ -1099,7 +1138,8 @@ async function main() { 'last-sequence': { type: 'string' }, 'count': { type: 'string' }, 'confirm': { type: 'boolean' }, - 'reset': { type: 'boolean' } + 'reset': { type: 'boolean' }, + 'duration': { type: 'string' }, }, allowPositionals: true }); @@ -1269,7 +1309,8 @@ async function handleLogCommand(subcommand: string, args: Record; - abstract getLogs(instanceId: string): Promise; + abstract getLogs(instanceId: string, onlyRecent?: boolean, durationSeconds?: number): Promise; // ========================================== // COMMAND EXECUTION (Required) diff --git a/worker/services/sandbox/remoteSandboxService.ts b/worker/services/sandbox/remoteSandboxService.ts index 119e4499..69acc93f 100644 --- a/worker/services/sandbox/remoteSandboxService.ts +++ b/worker/services/sandbox/remoteSandboxService.ts @@ -241,8 +241,12 @@ export class RemoteSandboxServiceClient extends BaseSandboxService{ /** * Get logs from a runner instance */ - async getLogs(instanceId: string): Promise { - return this.makeRequest(`/instances/${instanceId}/logs`, 'GET'); + async getLogs(instanceId: string, onlyRecent?: boolean, durationSeconds?: number): Promise { + const params = new URLSearchParams(); + if (onlyRecent) params.append('reset', 'true'); + if (durationSeconds) params.append('duration', durationSeconds.toString()); + const queryString = params.toString() ? `?${params.toString()}` : ''; + return this.makeRequest(`/instances/${instanceId}/logs${queryString}`, 'GET'); } // temp, debug diff --git a/worker/services/sandbox/sandboxSdkClient.ts b/worker/services/sandbox/sandboxSdkClient.ts index 3af2ab1e..fd0e4e5d 100644 --- a/worker/services/sandbox/sandboxSdkClient.ts +++ b/worker/services/sandbox/sandboxSdkClient.ts @@ -1411,11 +1411,12 @@ export class SandboxSdkClient extends BaseSandboxService { // ========================================== // LOG RETRIEVAL // ========================================== - async getLogs(instanceId: string, onlyRecent?: boolean): Promise { + async getLogs(instanceId: string, onlyRecent?: boolean, durationSeconds?: number): Promise { try { - this.logger.info('Retrieving instance logs', { instanceId }); + this.logger.info('Retrieving instance logs', { instanceId, durationSeconds }); // Use CLI to get all logs and reset the file - const cmd = `timeout 10s monitor-cli logs get -i ${instanceId} --format raw ${onlyRecent ? '--reset' : ''}`; + const durationArg = durationSeconds ? `--duration ${durationSeconds}` : ''; + const cmd = `timeout 10s monitor-cli logs get -i ${instanceId} --format raw ${onlyRecent ? '--reset' : ''} ${durationArg}`; const result = await this.executeCommand(instanceId, cmd, 15000); return { success: true, From 41fab814b6991895da1cd9eccb2acac00a6f79a4 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 15:36:12 -0400 Subject: [PATCH 013/150] feat: add tool result field to websocket messages and update event handling --- .../chat/utils/handle-websocket-message.ts | 6 ++- src/routes/chat/utils/message-helpers.ts | 49 ++++++++++++------- worker/api/websocketTypes.ts | 1 + 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/routes/chat/utils/handle-websocket-message.ts b/src/routes/chat/utils/handle-websocket-message.ts index 5e6f7010..b3ffb9f7 100644 --- a/src/routes/chat/utils/handle-websocket-message.ts +++ b/src/routes/chat/utils/handle-websocket-message.ts @@ -677,7 +677,11 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { if (message.tool) { const tool = message.tool; - setMessages(prev => appendToolEvent(prev, conversationId, { name: tool.name, status: tool.status })); + setMessages(prev => appendToolEvent(prev, conversationId, { + name: tool.name, + status: tool.status, + result: tool.result + })); break; } diff --git a/src/routes/chat/utils/message-helpers.ts b/src/routes/chat/utils/message-helpers.ts index a4816f4f..5f581099 100644 --- a/src/routes/chat/utils/message-helpers.ts +++ b/src/routes/chat/utils/message-helpers.ts @@ -149,13 +149,13 @@ export function handleStreamingMessage( /** * Append or update a tool event * - Tool 'start': Add with current position for inline rendering - * - Tool 'success': Update matching 'start' to 'success' in place OR add new success event if content changed + * - Tool 'success': Update matching 'start' to 'success' in place (always updates, never adds new) * - Tool 'error': Add error event with position for inline rendering */ export function appendToolEvent( messages: ChatMessage[], conversationId: string, - tool: { name: string; status: 'start' | 'success' | 'error' } + tool: { name: string; status: 'start' | 'success' | 'error'; result?: string } ): ChatMessage[] { const idx = messages.findIndex(m => m.conversationId === conversationId && m.role === 'assistant'); const timestamp = Date.now(); @@ -206,33 +206,47 @@ export function appendToolEvent( if (startEventIndex !== -1) { const startEvent = current[startEventIndex]; + const contentChanged = startEvent.contentLength !== currentContentLength; + const isDeepDebug = tool.name === 'deep_debug'; - // If no content after start, update in place - if (startEvent.contentLength === currentContentLength) { + // For deep_debug with content changes: add new success event at end (chronological) + // For other tools: update in place (avoid duplication) + if (isDeepDebug && contentChanged) { + // Remove start event and add success event at current position return { ...m, ui: { ...m.ui, - toolEvents: current.map((ev, j) => - j === startEventIndex - ? { name: ev.name, status: 'success' as const, timestamp, contentLength: currentContentLength } - : ev - ) + toolEvents: [ + ...current.filter((_, j) => j !== startEventIndex), + { + name: tool.name, + status: 'success' as const, + timestamp, + contentLength: currentContentLength, + result: tool.result + } + ] } }; } - // Content changed, add new success event at current position + // Update in place for other tools or when no content changed return { ...m, ui: { ...m.ui, - toolEvents: [...current, { - name: tool.name, - status: 'success', - timestamp, - contentLength: currentContentLength - }] + toolEvents: current.map((ev, j) => + j === startEventIndex + ? { + name: ev.name, + status: 'success' as const, + timestamp: startEvent.timestamp, // Keep original timestamp for stable React key + contentLength: ev.contentLength, // Keep original position + result: tool.result // Add result if provided + } + : ev + ) } }; } @@ -246,7 +260,8 @@ export function appendToolEvent( name: tool.name, status: 'success', timestamp, - contentLength: currentContentLength + contentLength: currentContentLength, + result: tool.result }] } }; diff --git a/worker/api/websocketTypes.ts b/worker/api/websocketTypes.ts index 1a92294e..72df30d5 100644 --- a/worker/api/websocketTypes.ts +++ b/worker/api/websocketTypes.ts @@ -320,6 +320,7 @@ type ConversationResponseMessage = { name: string; status: 'start' | 'success' | 'error'; args?: Record; + result?: string; }; }; From 8d00cd063a638148d04673bcb7227b95cc3823db Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 15:36:24 -0400 Subject: [PATCH 014/150] refactor: enhance code debugger with loop detection and improved prompts --- worker/agents/assistants/codeDebugger.ts | 527 ++++++++++++++++++----- 1 file changed, 423 insertions(+), 104 deletions(-) diff --git a/worker/agents/assistants/codeDebugger.ts b/worker/agents/assistants/codeDebugger.ts index da2faa60..f66f58c5 100644 --- a/worker/agents/assistants/codeDebugger.ts +++ b/worker/agents/assistants/codeDebugger.ts @@ -1,10 +1,10 @@ import Assistant from './assistant'; import { ConversationMessage, - createAssistantMessage, - createSystemMessage, - createUserMessage, - Message, + createAssistantMessage, + createSystemMessage, + createUserMessage, + Message, } from '../inferutils/common'; import { executeInference } from '../inferutils/infer'; import { InferenceContext, ModelConfig } from '../inferutils/config.types'; @@ -15,128 +15,447 @@ import { AGENT_CONFIG } from '../inferutils/config'; import { buildDebugTools } from '../tools/customTools'; import { RenderToolCall } from '../operations/UserConversationProcessor'; import { IdGenerator } from '../utils/idGenerator'; +import { PROMPT_UTILS } from '../prompts'; +import { RuntimeError } from 'worker/services/sandbox/sandboxTypes'; +import { FileState } from '../core/state'; -const SYSTEM_PROMPT = `You are an autonomous code debugging assistant. -Goal: find root-cause fast and apply minimal, surgical fixes. +const SYSTEM_PROMPT = `You are an elite autonomous code debugging specialist with deep expertise in root-cause analysis, modern web frameworks (React, Next.js, Vite), TypeScript/JavaScript, build tools, and runtime environments. -Use tools to: -- get_logs: fetch runtime errors -- run_analysis: lint + typecheck (optionally scoped to files) -- read_files: read file contents by RELATIVE paths (batch multiple files in one call) -- exec_commands: run shell commands from project root (no cd needed) -- regenerate_file: apply surgical fixes to specific files -- deploy_preview: redeploy when fixes are applied +## CRITICAL: Communication Mode +**You are configured with HIGH reasoning capability. Use it.** +- Conduct ALL analysis, planning, and reasoning INTERNALLY +- Output should be CONCISE: brief status updates and tool calls only +- NO verbose explanations, step-by-step narrations, or lengthy thought processes in output +- Think deeply internally → Act decisively externally → Report briefly -Context rules (IMPORTANT): -- All file paths are RELATIVE to the project root (sandbox pwd = project directory) -- Commands execute from project root automatically (no cd needed) -- Prefer batching/parallel tool calls (e.g., read multiple files together) +## Project Environment +You are working on a **Cloudflare Workers** project (optionally with Durable Objects). Key characteristics: +- **Runtime**: Cloudflare Workers runtime (V8 isolates, not Node.js) +- **No Node.js APIs**: No fs, path, process, etc. Use Workers APIs instead +- **Request/Response**: Uses Fetch API standard (Request, Response, fetch) +- **Durable Objects**: Stateful objects with transactional storage API when present +- **Build**: Typically uses Vite or similar for bundling +- **Deployment**: via wrangler to Cloudflare edge -Guardrails: -- Prevent React render loops (state-in-render, missing deps, unstable Zustand selectors) -- Ensure import/export integrity (named vs default) +**CRITICAL CONSTRAINTS:** +- **NEVER edit wrangler.jsonc or package.json** - these are locked/managed externally +- If you think the issue requires changing these files, report it's impossible to fix -Strategy: get_logs/run_analysis → read suspect files → apply fixes → verify → repeat until clean -Keep output concise. Act decisively. Stop when errors are cleared or stuck.`; +## How the Platform & Logs Work (IMPORTANT) +This is an AI coding platform with a sandbox environment: +- **Sandbox Preview**: Apps run in a Cloudflare Workers sandbox with a live preview URL +- **Logs are USER-DRIVEN**: Runtime logs (get_logs) only appear when the USER interacts with the app + - After you deploy changes, logs won't appear until the user actually clicks buttons, navigates pages, etc. + - If you add console.log statements and deploy, you MUST wait for user interaction to see those logs + - **DO NOT repeatedly check logs expecting new output if the user hasn't interacted with the app** -const USER_PROMPT = (issue: string, fileSummaries: string) => `Issue to debug: -${issue} +**CRITICAL WORKFLOW for Runtime Verification:** +1. Deploy changes: deploy_preview +2. Wait for interaction: wait(20-30, "Waiting for user to interact") +3. Check logs: get_logs +4. If logs empty, user hasn't interacted - inform them and wait longer OR use static analysis + +- **Static Analysis is IMMEDIATE**: run_analysis doesn't require user interaction - use this for verification. But you need to deploy changes first to meaningfully run static analysis +- **When logging isn't working**: If you need to debug but logs aren't appearing: + - State clearly: "I've added logging. Please interact with the app (click buttons, navigate) to generate logs, then I can continue." + - OR use static analysis and code review instead of relying on runtime logs + - Don't get stuck in a loop trying to check logs when user hasn't interacted + +**Always make sure to deploy your changes before running static analysis or fetching logs** + +## Your Approach +You are methodical and evidence-based. You choose your own path to solve issues, but always verify fixes work before claiming success. + +**CRITICAL - Internal Reasoning:** +- You have advanced reasoning capabilities - USE THEM +- Think deeply internally rather than explaining every step +- Analyze code, trace execution paths, and form hypotheses in your internal reasoning +- Only output concise, actionable information - not lengthy explanations +- Your reasoning_effort is set to HIGH - leverage this for complex analysis + +**Required Workflow:** +1. Run initial diagnostic tools (run_analysis, get_logs, or read_files) +2. **Internally create a debugging plan** - analyze in your reasoning, don't output verbose plans +3. **Execute decisively** - Make tool calls with minimal commentary +4. **Verify fixes** - Call run_analysis or get_logs after fixes +5. **Provide concise final report** - Brief summary of what was done + +## Available Tools +Use these tools flexibly based on what you need: + +- **get_logs**: Fetch runtime errors and console output from Workers runtime +- **get_runtime_errors**: Fetch latest runtime errors from sandbox storage (user-interaction driven, may be stale) +- **run_analysis**: Run lint + typecheck (optionally scope to specific files) +- **read_files**: Read file contents by RELATIVE paths (batch multiple in one call for efficiency) +- **exec_commands**: Execute shell commands from project root (no cd needed) +- **regenerate_file**: Autonomous surgical code fixer - see detailed guide below +- **deploy_preview**: Deploy to Cloudflare Workers preview environment to verify fixes +- **wait**: Sleep for N seconds (use after deploy to allow time for user interaction before checking logs) + +## How to Use regenerate_file (CRITICAL) + +**What it is:** +- An autonomous AI agent that applies surgical fixes to code files +- Makes minimal, targeted changes to fix specific issues +- Returns a diff showing exactly what changed +- Makes multiple passes (up to 5) to ensure issues are fixed +- Uses intelligent SEARCH-REPLACE pattern matching internally + +**Parameters:** +\`\`\`typescript +regenerate_file({ + path: "relative/path/to/file.ts", + issues: [ + "Issue 1: Detailed description of the problem", + "Issue 2: Another specific issue to fix", + // ... more issues + ] +}) +\`\`\` + +**How to describe issues (CRITICAL for success):** +- **BE SPECIFIC**: Include exact error messages, line references, or code snippets +- **ONE PROBLEM PER ISSUE**: Don't combine multiple unrelated problems +- **PROVIDE CONTEXT**: Explain what's broken and why it's a problem +- **USE CONCRETE DETAILS**: Not "fix the bug" but "Fix TypeError: Cannot read property 'items' of undefined on line 45" + +**Good Examples:** +\`\`\`javascript +issues: [ + "Fix TypeError: Cannot read property 'items' of undefined - add null check before accessing data.items", + "Fix infinite render loop in useEffect - add missing dependency array to useEffect on line 23", + "Fix incorrect API endpoint path - change '/api/todo' to '/api/todos' to match backend routes", +] +\`\`\` + +**Bad Examples (DON'T DO THIS):** +\`\`\`javascript +issues: [ + "Fix the code", // ❌ Too vague + "Make it work", // ❌ No specifics + "There's a bug in line 45 and also the imports are wrong and the function signature is bad", // ❌ Multiple issues combined +] +\`\`\` + +**What regenerate_file returns:** +\`\`\`typescript +{ + path: "the/file/path.ts", + diff: "Unified diff showing changes:\n@@ -23,1 +23,1 @@\n-const x = data.items\n+const x = data?.items || []" +} +\`\`\` + +**CRITICAL: After calling regenerate_file:** +1. **READ THE DIFF** - Always examine what changed +2. **VERIFY THE FIX** - Check if the diff addresses the reported issues +3. **DON'T REGENERATE AGAIN** if the diff shows the fix was already applied +4. **RUN run_analysis** after fixes to verify no new errors were introduced + +**When to use regenerate_file:** +- ✅ TypeScript/JavaScript errors that need code changes +- ✅ Runtime errors that require logic fixes +- ✅ Missing null checks, undefined handling +- ✅ React infinite loops (useEffect dependencies, etc.) +- ✅ Import/export errors +- ✅ API endpoint mismatches + +**When NOT to use regenerate_file:** +- ❌ Files that don't exist yet (file must exist first) +- ❌ wrangler.jsonc or package.json (these are locked) +- ❌ Configuration issues that need different tools +- ❌ When you haven't read the file yet (read it first!) +- ❌ When the same issue has already been fixed (check diff!) + +## File Path Rules (CRITICAL) +- All paths are RELATIVE to project root (sandbox pwd = project directory) +- Commands execute from project root automatically +- Never use 'cd' commands +- Prefer batching parallel tool calls when possible + +## Core Principles + +**Pay Attention to Tool Results** +- **CRITICAL**: Always read and understand what tools return, especially: + - regenerate_file returns 'diff' showing exactly what changed - review it before claiming you misread something + - If the diff shows the code already has what you wanted, DON'T regenerate again + - run_analysis returns specific errors - read them carefully + - get_logs shows actual runtime behavior - analyze what's happening +- **Before calling regenerate_file**: Read the current file content first to confirm the issue exists +- **After calling regenerate_file**: Check the returned diff to verify the change was correct + +**Verification is Mandatory** +- First thoroughly and deeply debug and verify if the problem actually exists and your theory is correct +- After applying any fix, ALWAYS verify it worked via get_logs or run_analysis +- Never claim success without proof +- If errors persist, iterate with a different approach +- get_logs would return the last X seconds of logs, but these might contain stale logs as well. Always cross reference timestamps of logs with timestamps of project updates or past messages to verify if the logs are relevant + +**Minimize Changes** +- Apply surgical, minimal fixes - change only what's necessary and when you are absolutely sure of it +- Fix root cause, not symptoms +- Avoid refactoring unless directly required +- Don't make changes "just in case" - only fix actual confirmed problems + +**Action-Oriented: Execute, Don't Just Explain** +- **CRITICAL**: Don't say "Let's do X" or "I will do X" and then stop - ACTUALLY DO IT +- After identifying a fix, immediately call the appropriate tool (regenerate_file, etc.) +- NO verbose explanations - think internally, act decisively +- Execute first, explain minimally +- Don't narrate your process - just do the work + +**Communication Style** +- Be CONCISE - brief status updates only +- Use internal reasoning for analysis, not verbose output +- When reading files or analyzing: think internally, output findings briefly +- When making fixes: call the tool, state what you're fixing in one line +- Save detailed explanations ONLY for the final report + +**Common Pitfalls to Avoid** +- **Cloudflare Workers**: No Node.js APIs (no fs, path, process, __dirname, etc.) +- **Workers Runtime**: Global state doesn't persist between requests (use Durable Objects for state) +- **Async operations**: Workers have CPU time limits, avoid long-running synchronous operations +- **React**: render loops (state-in-render, missing deps, unstable Zustand selectors) +- **Import/export**: named vs default inconsistency +- **Type safety**: maintain strict TypeScript compliance +- **Configuration files**: Never try to edit wrangler.jsonc or package.json + +## Success Criteria +You're done when: +1. ✅ Errors cleared AND verified via logs/analysis +2. 🔄 Genuinely stuck after trying 3+ different approaches +3. ❌ Task impossible with available tools (e.g., requires editing wrangler.jsonc or package.json) + +**You are NOT done if:** +- ❌ You identified issues but didn't apply fixes +- ❌ You said "Let's fix X" but didn't call regenerate_file +- ❌ You explained what should be done without doing it +- ❌ You applied fixes but didn't verify them + +**When you complete the task:** +1. State: "TASK_COMPLETE: [brief summary]" +2. Provide a concise final report: + - Issues found and root cause + - Fixes applied (file paths) + - Verification results + - Current state + +**If stuck:** "TASK_STUCK: [reason]" + what you tried + +## Working Style +- Use your internal reasoning - think deeply, output concisely +- Be decisive - analyze internally, act externally +- No play-by-play narration - just execute +- Quality through internal reasoning, not verbose output + +The goal is working code, verified through evidence. Think internally, act decisively.`; + +const USER_PROMPT = ( + issue: string, + fileSummaries: string, + templateInfo?: string, + runtimeErrors?: string +) => `## Debugging Task +**Issue to resolve:** ${issue} + +## Project Context +Below is metadata about the codebase. Use this to orient yourself, but read actual file contents when you need details. -Project files (metadata only): ${fileSummaries} -Strategy: -1) get_logs, run_analysis (scope to suspect files when known) -2) locate suspect files; read_files only when needed (RELATIVE paths, batch reads) -3) if fix needed, call regenerate_file with precise issues (minimal diff) -4) recheck errors/analysis; repeat until clean or stuck -Reply concisely with steps taken, findings, and results.`; - -export type FileIndexEntry = { - path: string; - purpose?: string; - changes?: string | null; +${templateInfo ? `## Template/Boilerplate Information +This project was built from a template with preconfigured components and utilities: + +${templateInfo} + +**IMPORTANT:** These are the available components, utilities, and APIs in the project. Always verify imports against this list.` : ''} + +${runtimeErrors ? `## Latest Runtime Errors (May be stale) +These runtime errors were captured from the sandbox. Note that they may be a few seconds old and are driven by user interactions with the app. + +**CRITICAL:** Runtime errors only appear when users interact with the app (clicking buttons, navigating, etc.). If you need fresh errors: +1. Deploy your changes with deploy_preview +2. Use wait(20-30) to allow time for user interaction +3. Then call get_runtime_errors to fetch latest errors + +${runtimeErrors}` : ''} + +## Your Mission +Diagnose and fix this issue. + +**Approach:** +- Think deeply internally (you have high reasoning capability) +- Execute decisively with minimal commentary +- Verify fixes before concluding +- Report concisely + +**Remember:** Use internal reasoning for analysis. Output only concise status updates and tool calls. Save explanations for the final report. + +Begin.`; + +type ToolCallRecord = { + toolName: string; + args: string; // JSON stringified args for comparison + timestamp: number; +}; + +type LoopDetectionState = { + recentCalls: ToolCallRecord[]; + repetitionWarnings: number; }; export type DebugSession = { - filesIndex: FileIndexEntry[]; - agent: CodingAgentInterface; + filesIndex: FileState[]; + agent: CodingAgentInterface; + runtimeErrors?: RuntimeError[]; }; export type DebugInputs = { - issue: string; + issue: string; }; -function summarizeFiles(files: FileIndexEntry[], max = 120): string { - const compact = files - .slice(0, max) - .map( - (f) => - `- ${f.path}${f.purpose ? ` — ${f.purpose}` : ''}${f.changes ? ` (changes: ${truncate(f.changes, 80)})` : ''}`, - ) - .join('\n'); - const extra = - files.length > max ? `\n...and ${files.length - max} more` : ''; - return compact + extra; +function summarizeFiles(files: FileState[], max = 120): string { + const compact = files + .slice(0, max) + .map((f) => { + const purpose = f.filePurpose ? ` — ${f.filePurpose}` : ''; + // const changes = f.lastDiff ? ` (recent changes)` : ''; + return `- ${f.filePath}${purpose}`; + }) + .join('\n'); + const extra = files.length > max ? `\n...and ${files.length - max} more` : ''; + return compact + extra; } -function truncate(s?: string | null, n: number = 120): string { - if (!s) return ''; - return s.length > n ? s.slice(0, n) + '…' : s; -} export class DeepCodeDebugger extends Assistant { - logger = createObjectLogger(this, 'DeepCodeDebugger'); - modelConfigOverride?: ModelConfig; - - constructor( - env: Env, - inferenceContext: InferenceContext, - modelConfigOverride?: ModelConfig, - ) { - super(env, inferenceContext); - this.modelConfigOverride = modelConfigOverride; - } + logger = createObjectLogger(this, 'DeepCodeDebugger'); + modelConfigOverride?: ModelConfig; + private loopDetection: LoopDetectionState = { + recentCalls: [], + repetitionWarnings: 0, + }; - async run( - inputs: DebugInputs, - session: DebugSession, - streamCb?: (chunk: string) => void, - toolRenderer?: RenderToolCall, - ): Promise { - const fileSummaries = summarizeFiles(session.filesIndex); - const system = createSystemMessage(SYSTEM_PROMPT); - const user = createUserMessage( - USER_PROMPT(inputs.issue, fileSummaries), - ); - const messages: Message[] = this.save([system, user]); - - const logger = this.logger; - const tools: ToolDefinition[] = buildDebugTools( - session, - logger, - toolRenderer, - ); - - const result = await executeInference({ - env: this.env, - context: this.inferenceContext, - agentActionName: 'deepDebugger', - modelConfig: this.modelConfigOverride || AGENT_CONFIG.deepDebugger, - messages, - tools, - stream: streamCb - ? { chunk_size: 64, onChunk: (c) => streamCb(c) } - : undefined, - temperature: 0.2, - // reasoning_effort: 'low', - }); - - const out = result?.string || ''; - this.save([createAssistantMessage(out)]); - return out; - } + constructor( + env: Env, + inferenceContext: InferenceContext, + modelConfigOverride?: ModelConfig, + ) { + super(env, inferenceContext); + this.modelConfigOverride = modelConfigOverride; + } + + private detectRepetition(toolName: string, args: Record): boolean { + const argsStr = JSON.stringify(args); + const now = Date.now(); + + // Keep only recent calls (last 10 minutes) + this.loopDetection.recentCalls = this.loopDetection.recentCalls.filter( + (call) => now - call.timestamp < 600000, + ); + + // Count how many times this exact call was made recently + const matchingCalls = this.loopDetection.recentCalls.filter( + (call) => call.toolName === toolName && call.args === argsStr, + ); + + // Record this call + this.loopDetection.recentCalls.push({ toolName, args: argsStr, timestamp: now }); + + // Repetition detected if same call made 3+ times + return matchingCalls.length >= 2; + } + + private injectLoopWarning(toolName: string): void { + this.loopDetection.repetitionWarnings++; + + const warningMessage = ` +⚠️ CRITICAL: REPETITION DETECTED - TOOL CALL BLOCKED + +You just attempted to execute "${toolName}" with identical arguments for the ${this.loopDetection.repetitionWarnings}th time. + +This tool call was BLOCKED to prevent an infinite loop. + +REQUIRED ACTIONS: +1. If your task is complete, state "TASK_COMPLETE: [summary]" and STOP +2. If not complete, try a DIFFERENT approach: + - Use different tools + - Use different arguments + - Read different files + - Apply a different fix strategy + +DO NOT repeat the same action. The definition of insanity is doing the same thing expecting different results. + +If you're genuinely stuck after trying 3 different approaches, honestly report: "TASK_STUCK: [reason]"`; + + this.save([createUserMessage(warningMessage)]); + } + + async run( + inputs: DebugInputs, + session: DebugSession, + streamCb?: (chunk: string) => void, + toolRenderer?: RenderToolCall, + ): Promise { + const fileSummaries = summarizeFiles(session.filesIndex); + + // Fetch template details from agent + const operationOptions = session.agent.getOperationOptions(); + const templateInfo = operationOptions.context.templateDetails + ? PROMPT_UTILS.serializeTemplate(operationOptions.context.templateDetails) + : undefined; + + const system = createSystemMessage(SYSTEM_PROMPT); + const user = createUserMessage( + USER_PROMPT(inputs.issue, fileSummaries, templateInfo, session.runtimeErrors ? PROMPT_UTILS.serializeErrors(session.runtimeErrors) : undefined) + ); + const messages: Message[] = this.save([system, user]); + + const logger = this.logger; + + // Wrap tools with loop detection + const rawTools = buildDebugTools(session, logger, toolRenderer); + const tools: ToolDefinition[] = rawTools.map((tool) => ({ + ...tool, + implementation: async (args: any) => { + // Check for repetition before executing + if (this.detectRepetition(tool.function.name, args)) { + this.logger.warn(`Loop detected for tool: ${tool.function.name}`); + this.injectLoopWarning(tool.function.name); + + // CRITICAL: Block execution to prevent infinite loops + return { + error: `Loop detected: You've called ${tool.function.name} with the same arguments multiple times. Try a different approach or stop if the task is complete.` + }; + } + + // Only execute if no loop detected + return await tool.implementation(args); + }, + })); + + const result = await executeInference({ + env: this.env, + context: this.inferenceContext, + agentActionName: 'deepDebugger', + modelConfig: this.modelConfigOverride || AGENT_CONFIG.deepDebugger, + messages, + tools, + stream: streamCb + ? { chunk_size: 64, onChunk: (c) => streamCb(c) } + : undefined, + }); + + const out = result?.string || ''; + + // Check for completion signals to prevent unnecessary continuation + if (out.includes('TASK_COMPLETE') || out.includes('Mission accomplished') || out.includes('TASK_STUCK')) { + this.logger.info('Agent signaled task completion or stuck state, stopping'); + } + + this.save([createAssistantMessage(out)]); + return out; + } - getTranscript(): ConversationMessage[] { + getTranscript(): ConversationMessage[] { return this.getHistory().map((m) => ({ ...m, conversationId: IdGenerator.generateConversationId(), From 52d86bab2eac71584fa800a49ac6da023edb1531 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 15:36:39 -0400 Subject: [PATCH 015/150] refactor: move generation state management from flags to Promise tracking --- worker/agents/core/simpleGeneratorAgent.ts | 35 +++++++++++----------- worker/agents/core/state.ts | 1 - worker/agents/core/websocket.ts | 16 ++++------ 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index c5510590..e6b0b7b8 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -105,6 +105,7 @@ export class SimpleCodeGeneratorAgent extends Agent { // In-memory storage for user-uploaded images (not persisted in DO state) // These are temporary and will be lost if the DO is evicted private pendingUserImages: ProcessedImageAttachment[] = [] + private generationPromise: Promise | null = null; protected operations: Operations = { codeReview: new CodeReviewOperation(), @@ -115,8 +116,6 @@ export class SimpleCodeGeneratorAgent extends Agent { fastCodeFixer: new FastCodeFixerOperation(), processUserMessage: new UserConversationProcessor() }; - - isGenerating: boolean = false; // Deployment queue management to prevent concurrent deployments private currentDeploymentPromise: Promise | null = null; @@ -153,7 +152,6 @@ export class SimpleCodeGeneratorAgent extends Agent { generatedPhases: [], generatedFilesMap: {}, agentMode: 'deterministic', - generationPromise: undefined, sandboxInstanceId: undefined, templateDetails: {} as TemplateDetails, commandsHistory: [], @@ -396,7 +394,7 @@ export class SimpleCodeGeneratorAgent extends Agent { } isCodeGenerating(): boolean { - return this.isGenerating; + return this.generationPromise !== null; } rechargePhasesCounter(max_phases: number = MAX_PHASES): void { @@ -491,12 +489,15 @@ export class SimpleCodeGeneratorAgent extends Agent { this.logger().info("Code generation already completed and no user inputs pending"); return; } - if (this.isGenerating) { + if (this.isCodeGenerating()) { this.logger().info("Code generation already in progress"); return; } - this.isGenerating = true; + this.generationPromise = this.launchStateMachine(reviewCycles); + await this.generationPromise; + } + private async launchStateMachine(reviewCycles: number) { this.broadcast(WebSocketMessageResponses.GENERATION_STARTED, { message: 'Starting code generation', totalFiles: this.getTotalFiles() @@ -575,7 +576,7 @@ export class SimpleCodeGeneratorAgent extends Agent { status: 'completed', } ); - this.isGenerating = false; + this.generationPromise = null; this.broadcast(WebSocketMessageResponses.GENERATION_COMPLETE, { message: "Code generation and review process completed.", instanceId: this.state.sandboxInstanceId, @@ -1156,15 +1157,15 @@ export class SimpleCodeGeneratorAgent extends Agent { this.getOperationOptions() ); - this.fileManager.saveGeneratedFile(result); + const fileState = this.fileManager.saveGeneratedFile(result); this.broadcast(WebSocketMessageResponses.FILE_REGENERATED, { message: `Regenerated file: ${file.filePath}`, - file: result, + file: fileState, original_issues: issues, }); - return result; + return fileState; } getTotalFiles(): number { @@ -1734,7 +1735,7 @@ export class SimpleCodeGeneratorAgent extends Agent { return await this.getSandboxServiceClient().executeCommands(sandboxInstanceId, commands, timeout); } - async regenerateFileByPath(path: string, issues: string[]): Promise<{ path: string; updatedPreview: string }> { + async regenerateFileByPath(path: string, issues: string[]): Promise<{ path: string; diff: string }> { const { sandboxInstanceId } = this.state; if (!sandboxInstanceId) { throw new Error('No sandbox instance available'); @@ -1760,7 +1761,7 @@ export class SimpleCodeGeneratorAgent extends Agent { const regenerated = await this.regenerateFile({ filePath: path, fileContents, filePurpose }, issues, 0); // Persist to sandbox instance await this.getSandboxServiceClient().writeFiles(sandboxInstanceId, [{ filePath: regenerated.filePath, fileContents: regenerated.fileContents }], `Deep debugger fix: ${path}`); - return { path, updatedPreview: regenerated.fileContents.slice(0, 4000) }; + return { path, diff: regenerated.lastDiff }; } async waitForPreview(): Promise { @@ -2146,9 +2147,9 @@ export class SimpleCodeGeneratorAgent extends Agent { } async waitForGeneration(): Promise { - if (this.state.generationPromise) { + if (this.generationPromise) { try { - await this.state.generationPromise; + await this.generationPromise; this.logger().info("Code generation completed successfully"); } catch (error) { this.logger().error("Error during code generation:", error); @@ -2442,8 +2443,8 @@ export class SimpleCodeGeneratorAgent extends Agent { }); } - async getLogs(_reset?: boolean): Promise { - const response = await this.getSandboxServiceClient().getLogs(this.state.sandboxInstanceId!); + async getLogs(_reset?: boolean, durationSeconds?: number): Promise { + const response = await this.getSandboxServiceClient().getLogs(this.state.sandboxInstanceId!, _reset, durationSeconds); if (response.success) { return `STDOUT: ${response.logs.stdout}\nSTDERR: ${response.logs.stderr}`; } else { @@ -2620,7 +2621,7 @@ export class SimpleCodeGeneratorAgent extends Agent { const { conversationResponse, conversationState } = conversationalResponse; this.setConversationState(conversationState); - if (!this.isGenerating) { + if (!this.generationPromise) { // If idle, start generation process this.logger().info('User input during IDLE state, starting generation'); this.generateAllFiles().catch(error => { diff --git a/worker/agents/core/state.ts b/worker/agents/core/state.ts index e636399f..54d6ac9a 100644 --- a/worker/agents/core/state.ts +++ b/worker/agents/core/state.ts @@ -33,7 +33,6 @@ export interface CodeGenState { blueprint: Blueprint; query: string; generatedFilesMap: Record; - generationPromise?: Promise; generatedPhases: PhaseState[]; commandsHistory?: string[]; // History of commands run lastPackageJson?: string; // Last package.json file contents diff --git a/worker/agents/core/websocket.ts b/worker/agents/core/websocket.ts index 7a7e926e..0a00f3f9 100644 --- a/worker/agents/core/websocket.ts +++ b/worker/agents/core/websocket.ts @@ -21,7 +21,7 @@ export function handleWebSocketMessage(agent: SimpleCodeGeneratorAgent, connecti }); // Check if generation is already active to avoid duplicate processes - if (agent.isGenerating) { + if (agent.isCodeGenerating()) { logger.info('Generation already in progress, skipping duplicate request'); // sendToConnection(connection, WebSocketMessageResponses.GENERATION_STARTED, { // message: 'Code generation is already in progress' @@ -37,7 +37,7 @@ export function handleWebSocketMessage(agent: SimpleCodeGeneratorAgent, connecti }).finally(() => { // Only clear shouldBeGenerating on successful completion // (errors might want to retry, so this could be handled differently) - if (!agent.isGenerating) { + if (!agent.isCodeGenerating()) { agent.setState({ ...agent.state, shouldBeGenerating: false @@ -46,7 +46,7 @@ export function handleWebSocketMessage(agent: SimpleCodeGeneratorAgent, connecti }); break; case WebSocketMessageRequests.CODE_REVIEW: - if (agent.isGenerating) { + if (agent.isCodeGenerating()) { sendError(connection, 'Cannot perform code review while generating files'); return; } @@ -122,14 +122,8 @@ export function handleWebSocketMessage(agent: SimpleCodeGeneratorAgent, connecti case WebSocketMessageRequests.STOP_GENERATION: // Clear shouldBeGenerating flag when user manually stops logger.info('Stopping code generation and clearing shouldBeGenerating flag'); - agent.setState({ - ...agent.state, - shouldBeGenerating: false - }); - // If there's an active generation, we should signal it to stop - // (This depends on how the generation process is implemented) - agent.isGenerating = false; + // TODO: Implement properly sendToConnection(connection, WebSocketMessageResponses.GENERATION_STOPPED, { message: 'Code generation stopped by user' @@ -143,7 +137,7 @@ export function handleWebSocketMessage(agent: SimpleCodeGeneratorAgent, connecti shouldBeGenerating: true }); - if (!agent.isGenerating) { + if (!agent.isCodeGenerating()) { sendToConnection(connection, WebSocketMessageResponses.GENERATION_RESUMED, { message: 'Code generation resumed' }); From 92f4e0e5192efd8ca0154e7d28ba6fd1f046a8da Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 15:37:09 -0400 Subject: [PATCH 016/150] feat: add wait_for_generation tool and enhance tool call result handling --- worker/agents/inferutils/config.ts | 2 +- .../operations/UserConversationProcessor.ts | 18 +++++++++++-- .../services/implementations/CodingAgent.ts | 20 +++++++++++--- .../services/implementations/FileManager.ts | 15 +++++++---- .../services/interfaces/ICodingAgent.ts | 12 ++++++--- worker/agents/tools/customTools.ts | 16 ++++++++--- .../tools/toolkit/clear-conversation.ts | 27 ------------------- 7 files changed, 65 insertions(+), 45 deletions(-) delete mode 100644 worker/agents/tools/toolkit/clear-conversation.ts diff --git a/worker/agents/inferutils/config.ts b/worker/agents/inferutils/config.ts index 552233db..ad7f530e 100644 --- a/worker/agents/inferutils/config.ts +++ b/worker/agents/inferutils/config.ts @@ -135,7 +135,7 @@ export const AGENT_CONFIG: AgentConfig = { name: AIModels.GEMINI_2_5_PRO, reasoning_effort: 'high', max_tokens: 32000, - temperature: 0.1, + temperature: 0.2, fallbackModel: AIModels.GEMINI_2_5_FLASH, }, codeReview: { diff --git a/worker/agents/operations/UserConversationProcessor.ts b/worker/agents/operations/UserConversationProcessor.ts index 90604a81..d55a6f2a 100644 --- a/worker/agents/operations/UserConversationProcessor.ts +++ b/worker/agents/operations/UserConversationProcessor.ts @@ -32,8 +32,10 @@ const COMPACTIFICATION_CONFIG = { export interface ToolCallStatusArgs { name: string; status: 'start' | 'success' | 'error'; - args?: Record + args?: Record; + result?: string; } + export type RenderToolCall = ( args: ToolCallStatusArgs ) => void; type ConversationResponseCallback = ( @@ -106,6 +108,7 @@ const SYSTEM_PROMPT = `You are Orange, the conversational AI interface for Cloud - queue_request: Queue modification requests for implementation in the next phase(s). Use for any feature/bug/change request. - get_logs: Fetch unread application logs from the sandbox to diagnose runtime issues. - deep_debug: Autonomous debugging assistant that investigates errors, reads files, runs commands, and applies targeted fixes. Use when users report bugs/errors that need immediate investigation and fixing. This transfers control to a specialized debugging agent. + - wait_for_generation: Wait for code generation to complete. Use when deep_debug returns GENERATION_IN_PROGRESS error. - deploy_preview: Redeploy or restart the preview when the user asks to deploy or the preview is blank/looping. - clear_conversation: Clear the current chat history for this session. - rename_project: Rename the project (lowercase letters, numbers, hyphens, underscores; 3-50 chars). @@ -133,6 +136,12 @@ Use the deep_debug tool to investigate and fix bugs immediately. This synchronou When you call deep_debug, it runs to completion and returns a transcript. The user will see all the debugging steps in real-time. After it returns, you can acknowledge completion: "The debugging session is complete. The issue should be resolved." +**CRITICAL - If deep_debug returns GENERATION_IN_PROGRESS error:** +1. Tell user: "Code generation is in progress. Let me wait for it to complete..." +2. Call wait_for_generation +3. Retry deep_debug +4. If it fails again, report the issue + **Option 2 - For feature requests or non-urgent fixes:** Queue the request via queue_request - the development agent will address it in the next phase. Then tell the user: "I'll fix this issue in the next phase or two." @@ -318,7 +327,12 @@ export class UserConversationProcessor extends AgentOperation ({ ...td, onStart: (args: Record) => toolCallRenderer({ name: td.function.name, status: 'start', args }), - onComplete: (args: Record, _result: unknown) => toolCallRenderer({ name: td.function.name, status: 'success', args }) + onComplete: (args: Record, result: unknown) => toolCallRenderer({ + name: td.function.name, + status: 'success', + args, + result: typeof result === 'string' ? result : JSON.stringify(result) + }) })); const runningHistory = await prepareMessagesForInference(env, conversationState.runningHistory); diff --git a/worker/agents/services/implementations/CodingAgent.ts b/worker/agents/services/implementations/CodingAgent.ts index 9bff8142..1bca670c 100644 --- a/worker/agents/services/implementations/CodingAgent.ts +++ b/worker/agents/services/implementations/CodingAgent.ts @@ -1,6 +1,6 @@ import { ProcessedImageAttachment } from "worker/types/image-attachment"; import { Blueprint } from "worker/agents/schemas"; -import { ExecuteCommandsResponse, StaticAnalysisResponse } from "worker/services/sandbox/sandboxTypes"; +import { ExecuteCommandsResponse, StaticAnalysisResponse, RuntimeError } from "worker/services/sandbox/sandboxTypes"; import { ICodingAgent } from "../interfaces/ICodingAgent"; import { OperationOptions } from "worker/agents/operations/common"; @@ -13,8 +13,12 @@ export class CodingAgentInterface { this.agentStub = agentStub; } - getLogs(reset?: boolean): Promise { - return this.agentStub.getLogs(reset); + getLogs(reset?: boolean, durationSeconds?: number): Promise { + return this.agentStub.getLogs(reset, durationSeconds); + } + + fetchRuntimeErrors(clear?: boolean): Promise { + return this.agentStub.fetchRuntimeErrors(clear); } async deployPreview(): Promise { @@ -69,7 +73,15 @@ export class CodingAgentInterface { } // Exposes a simplified regenerate API for tools - regenerateFile(path: string, issues: string[]): Promise<{ path: string; updatedPreview: string }> { + regenerateFile(path: string, issues: string[]): Promise<{ path: string; diff: string }> { return this.agentStub.regenerateFileByPath(path, issues); } + + isCodeGenerating(): boolean { + return this.agentStub.isCodeGenerating(); + } + + waitForGeneration(): Promise { + return this.agentStub.waitForGeneration(); + } } diff --git a/worker/agents/services/implementations/FileManager.ts b/worker/agents/services/implementations/FileManager.ts index 96d10c9f..a16ac6d4 100644 --- a/worker/agents/services/implementations/FileManager.ts +++ b/worker/agents/services/implementations/FileManager.ts @@ -4,6 +4,7 @@ import { IStateManager } from '../interfaces/IStateManager'; import { FileOutputType } from '../../schemas'; import { TemplateDetails } from '../../../services/sandbox/sandboxTypes'; import { FileProcessing } from '../../domain/pure/FileProcessing'; +import { FileState } from 'worker/agents/core/state'; /** * Manages file operations for code generation @@ -29,13 +30,14 @@ export class FileManager implements IFileManager { return FileProcessing.getAllFiles(state.templateDetails, state.generatedFilesMap); } - saveGeneratedFile(file: FileOutputType): void { - this.saveGeneratedFiles([file]); + saveGeneratedFile(file: FileOutputType): FileState { + return this.saveGeneratedFiles([file])[0]; } - saveGeneratedFiles(files: FileOutputType[]): void { + saveGeneratedFiles(files: FileOutputType[]): FileState[] { const state = this.stateManager.getState(); const filesMap = { ...state.generatedFilesMap }; + const fileStates: FileState[] = []; for (const file of files) { let lastDiff = ''; @@ -51,19 +53,22 @@ export class FileManager implements IFileManager { console.error(`Failed to generate diff for file ${file.filePath}:`, error); } } - filesMap[file.filePath] = { + const fileState = { ...file, lasthash: '', lastmodified: Date.now(), unmerged: [], lastDiff - }; + } + filesMap[file.filePath] = fileState; + fileStates.push(fileState); } this.stateManager.setState({ ...state, generatedFilesMap: filesMap }); + return fileStates; } deleteFiles(filePaths: string[]): void { diff --git a/worker/agents/services/interfaces/ICodingAgent.ts b/worker/agents/services/interfaces/ICodingAgent.ts index c523ec0b..3ecafb52 100644 --- a/worker/agents/services/interfaces/ICodingAgent.ts +++ b/worker/agents/services/interfaces/ICodingAgent.ts @@ -1,6 +1,6 @@ import { FileOutputType, Blueprint } from "worker/agents/schemas"; import { BaseSandboxService } from "worker/services/sandbox/BaseSandboxService"; -import { ExecuteCommandsResponse, PreviewType, StaticAnalysisResponse } from "worker/services/sandbox/sandboxTypes"; +import { ExecuteCommandsResponse, PreviewType, StaticAnalysisResponse, RuntimeError } from "worker/services/sandbox/sandboxTypes"; import { ProcessedImageAttachment } from "worker/types/image-attachment"; import { OperationOptions } from "worker/agents/operations/common"; @@ -11,7 +11,7 @@ export abstract class ICodingAgent { abstract deployToCloudflare(): Promise<{ deploymentUrl?: string; workersUrl?: string } | null>; - abstract getLogs(reset?: boolean): Promise; + abstract getLogs(reset?: boolean, durationSeconds?: number): Promise; abstract queueUserRequest(request: string, images?: ProcessedImageAttachment[]): void; @@ -29,5 +29,11 @@ export abstract class ICodingAgent { abstract execCommands(commands: string[], timeout?: number): Promise; - abstract regenerateFileByPath(path: string, issues: string[]): Promise<{ path: string; updatedPreview: string }>; + abstract regenerateFileByPath(path: string, issues: string[]): Promise<{ path: string; diff: string }>; + + abstract fetchRuntimeErrors(clear?: boolean): Promise; + + abstract isCodeGenerating(): boolean; + + abstract waitForGeneration(): Promise; } diff --git a/worker/agents/tools/customTools.ts b/worker/agents/tools/customTools.ts index 929d6edb..4ab2872a 100644 --- a/worker/agents/tools/customTools.ts +++ b/worker/agents/tools/customTools.ts @@ -8,7 +8,6 @@ import { createGetLogsTool } from './toolkit/get-logs'; import { createDeployPreviewTool } from './toolkit/deploy-preview'; import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; import { createDeepDebuggerTool } from "./toolkit/deep-debugger"; -import { createClearConversationTool } from './toolkit/clear-conversation'; import { createRenameProjectTool } from './toolkit/rename-project'; import { createAlterBlueprintTool } from './toolkit/alter-blueprint'; import { DebugSession } from '../assistants/codeDebugger'; @@ -16,6 +15,10 @@ import { createReadFilesTool } from './toolkit/read-files'; import { createExecCommandsTool } from './toolkit/exec-commands'; import { createRunAnalysisTool } from './toolkit/run-analysis'; import { createRegenerateFileTool } from './toolkit/regenerate-file'; +import { createWaitTool } from './toolkit/wait'; +import { createGetRuntimeErrorsTool } from './toolkit/get-runtime-errors'; +import { createWaitForGenerationTool } from './toolkit/wait-for-generation'; +import { RuntimeError } from 'worker/services/sandbox/sandboxTypes'; export async function executeToolWithDefinition( toolDef: ToolDefinition, @@ -43,7 +46,7 @@ export function buildTools( createQueueRequestTool(agent, logger), createGetLogsTool(agent, logger), createDeployPreviewTool(agent, logger), - createClearConversationTool(agent, logger), + createWaitForGenerationTool(agent, logger), createRenameProjectTool(agent, logger), createAlterBlueprintTool(agent, logger), // Deep autonomous debugging assistant tool @@ -54,11 +57,13 @@ export function buildTools( export function buildDebugTools(session: DebugSession, logger: StructuredLogger, toolRenderer?: RenderToolCall): ToolDefinition[] { const tools = [ createGetLogsTool(session.agent, logger), + createGetRuntimeErrorsTool(session.agent, logger), createReadFilesTool(session.agent, logger), createRunAnalysisTool(session.agent, logger), createExecCommandsTool(session.agent, logger), createRegenerateFileTool(session.agent, logger), createDeployPreviewTool(session.agent, logger), + createWaitTool(logger), ]; // Attach tool renderer for UI visualization if provided @@ -66,7 +71,12 @@ export function buildDebugTools(session: DebugSession, logger: StructuredLogger, return tools.map(td => ({ ...td, onStart: (args: Record) => toolRenderer({ name: td.function.name, status: 'start', args }), - onComplete: (args: Record, _result: unknown) => toolRenderer({ name: td.function.name, status: 'success', args }) + onComplete: (args: Record, result: unknown) => toolRenderer({ + name: td.function.name, + status: 'success', + args, + result: typeof result === 'string' ? result : JSON.stringify(result) + }) })); } diff --git a/worker/agents/tools/toolkit/clear-conversation.ts b/worker/agents/tools/toolkit/clear-conversation.ts deleted file mode 100644 index 5ac0a822..00000000 --- a/worker/agents/tools/toolkit/clear-conversation.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ToolDefinition } from '../types'; -import { StructuredLogger } from '../../../logger'; -import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; - -export function createClearConversationTool( - agent: CodingAgentInterface, - logger: StructuredLogger -): ToolDefinition, null> { - return { - type: 'function' as const, - function: { - name: 'clear_conversation', - description: 'Clear the current conversation history for this session.', - parameters: { - type: 'object', - properties: {}, - additionalProperties: false, - required: [], - }, - }, - implementation: async () => { - logger.info('Clearing conversation history'); - agent.clearConversation(); - return null; - }, - }; -} From b144e2c8b63bdf803f28b0ad85f8183ae6281316 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 15:37:27 -0400 Subject: [PATCH 017/150] feat: enhance debugging tools with user interaction handling and runtime error detection --- worker/agents/tools/toolkit/deep-debugger.ts | 48 ++++++-------- worker/agents/tools/toolkit/deploy-preview.ts | 2 +- worker/agents/tools/toolkit/get-logs.ts | 28 ++++++-- .../tools/toolkit/get-runtime-errors.ts | 65 +++++++++++++++++++ .../agents/tools/toolkit/regenerate-file.ts | 6 +- .../tools/toolkit/wait-for-generation.ts | 43 ++++++++++++ worker/agents/tools/toolkit/wait.ts | 48 ++++++++++++++ 7 files changed, 205 insertions(+), 35 deletions(-) create mode 100644 worker/agents/tools/toolkit/get-runtime-errors.ts create mode 100644 worker/agents/tools/toolkit/wait-for-generation.ts create mode 100644 worker/agents/tools/toolkit/wait.ts diff --git a/worker/agents/tools/toolkit/deep-debugger.ts b/worker/agents/tools/toolkit/deep-debugger.ts index 14fd0663..e2ba6dd1 100644 --- a/worker/agents/tools/toolkit/deep-debugger.ts +++ b/worker/agents/tools/toolkit/deep-debugger.ts @@ -1,12 +1,8 @@ import { ToolDefinition } from '../types'; import { StructuredLogger } from '../../../logger'; import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; -import { - DeepCodeDebugger, - type FileIndexEntry, -} from 'worker/agents/assistants/codeDebugger'; +import { DeepCodeDebugger } from 'worker/agents/assistants/codeDebugger'; import { RenderToolCall } from '../../operations/UserConversationProcessor'; -import { ConversationMessage } from '../../inferutils/common'; export function createDeepDebuggerTool( agent: CodingAgentInterface, @@ -15,14 +11,14 @@ export function createDeepDebuggerTool( toolRenderer?: RenderToolCall, ): ToolDefinition< { issue: string; focus_paths?: string[] }, - { transcript: ConversationMessage[] } | { error: string } + { transcript: string } | { error: string } > { return { type: 'function', function: { name: 'deep_debug', description: - 'Autonomous deep debugging assistant. Investigates runtime errors and static analysis, reads targeted files (relative paths), runs commands (in project root, no cd), and applies surgical fixes via regenerate_file. Returns a concise transcript.', + 'Autonomous debugging assistant that investigates errors, reads files, and applies fixes. CANNOT run during code generation - will return GENERATION_IN_PROGRESS error if generation is active.', parameters: { type: 'object', properties: { @@ -34,36 +30,34 @@ export function createDeepDebuggerTool( }, implementation: async ({ issue, focus_paths }: { issue: string; focus_paths?: string[] }) => { try { + // Check if code generation is in progress + if (agent.isCodeGenerating()) { + logger.warn('Cannot start debugging: Code generation in progress'); + return { + error: 'GENERATION_IN_PROGRESS: Code generation is currently running. Use wait_for_generation tool, then retry deep_debug.' + }; + } + const operationOptions = agent.getOperationOptions(); - const filesIndex: FileIndexEntry[] = - operationOptions.context.allFiles - .map((f) => ({ - path: f.filePath, - purpose: f.filePurpose, - changes: - f.lastDiff || - (Array.isArray(f.unmerged) && f.unmerged.length - ? f.unmerged.join('\n') - : null), - })) - .filter( - (f) => - !focus_paths?.length || - focus_paths.some((p) => f.path.includes(p)), - ); + const filesIndex = operationOptions.context.allFiles + .filter((f) => + !focus_paths?.length || + focus_paths.some((p) => f.filePath.includes(p)), + ); + + const runtimeErrors = await agent.fetchRuntimeErrors(true); const dbg = new DeepCodeDebugger( operationOptions.env, operationOptions.inferenceContext, ); - await dbg.run( + const out = await dbg.run( { issue }, - { filesIndex, agent }, + { filesIndex, agent, runtimeErrors }, streamCb ? (chunk) => streamCb(chunk) : undefined, toolRenderer, ); - const transcript = dbg.getTranscript(); - return { transcript }; + return { transcript: out }; } catch (e) { logger.error('Deep debugger failed', e); return { error: `Deep debugger failed: ${String(e)}` }; diff --git a/worker/agents/tools/toolkit/deploy-preview.ts b/worker/agents/tools/toolkit/deploy-preview.ts index 04c06dcd..c51f5ffc 100644 --- a/worker/agents/tools/toolkit/deploy-preview.ts +++ b/worker/agents/tools/toolkit/deploy-preview.ts @@ -15,7 +15,7 @@ export function createDeployPreviewTool( function: { name: 'deploy_preview', description: - 'Deploys the current application to a preview environment.', + 'Deploys the current application to a preview environment. After deployment, the app is live at the preview URL, but runtime logs (get_logs) will only appear when the user interacts with the app - not automatically after deployment. CRITICAL: After deploying, use wait(20-30) to allow time for user interaction before checking logs.', parameters: { type: 'object', properties: {}, diff --git a/worker/agents/tools/toolkit/get-logs.ts b/worker/agents/tools/toolkit/get-logs.ts index e9c5851e..74b6e571 100644 --- a/worker/agents/tools/toolkit/get-logs.ts +++ b/worker/agents/tools/toolkit/get-logs.ts @@ -4,6 +4,7 @@ import { CodingAgentInterface } from 'worker/agents/services/implementations/Cod type GetLogsArgs = { reset?: boolean; + durationSeconds?: number; }; type GetLogsResult = { logs: string } | ErrorResult; @@ -18,17 +19,34 @@ export function createGetLogsTool( name: 'get_logs', description: `Get the current application/server logs from the sandbox environment. Useful for debugging runtime issues, checking console output, or investigating errors. Clears logs every time when called. App\'s browser console warn/error messages are also piped here. -All unread logs would be streamed, so you need to match timestamps on your own to understand things.`, +All unread logs would be streamed, so you need to match timestamps on your own to understand things. + +IMPORTANT: Logs are USER-DRIVEN - they only appear when the user interacts with the app (clicks buttons, navigates pages, etc.). + +CRITICAL PATTERN: +- After deploy_preview: DO NOT immediately call get_logs +- Instead, if logs are needed: Call wait(20-30 seconds, "Waiting for user to interact with app") +- Then: Call get_logs to check results +- If logs are empty, it means user hasn't interacted yet - ask them to interact and wait longer + +If logs are not needed, don't call this. + +If you can't wait for user interaction, use static analysis (run_analysis) instead.`, parameters: { type: 'object', - properties: {}, + properties: { + durationSeconds: { + type: 'number', + description: 'Optional: Filter logs to only show entries from the last N seconds. If not specified, returns all logs till date. Useful to reduce noise when you only need recent activity.', + }, + }, required: [], }, }, - implementation: async (_args?) => { + implementation: async (args?) => { try { - logger.info('Fetching application logs'); - const logs = await agent.getLogs(); + logger.info('Fetching application logs', { durationSeconds: args?.durationSeconds }); + const logs = await agent.getLogs(false, args?.durationSeconds); return { logs }; } catch (error) { return { diff --git a/worker/agents/tools/toolkit/get-runtime-errors.ts b/worker/agents/tools/toolkit/get-runtime-errors.ts new file mode 100644 index 00000000..5c75374d --- /dev/null +++ b/worker/agents/tools/toolkit/get-runtime-errors.ts @@ -0,0 +1,65 @@ +import { ErrorResult, ToolDefinition } from '../types'; +import { StructuredLogger } from '../../../logger'; +import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; +import { RuntimeError } from 'worker/services/sandbox/sandboxTypes'; + +type GetRuntimeErrorsArgs = Record; + +type GetRuntimeErrorsResult = { errors: RuntimeError[] } | ErrorResult; + +export function createGetRuntimeErrorsTool( + agent: CodingAgentInterface, + logger: StructuredLogger +): ToolDefinition { + return { + type: 'function' as const, + function: { + name: 'get_runtime_errors', + description: + `Fetch latest runtime errors from the sandbox error storage. These are errors captured by the runtime error detection system. + +**IMPORTANT CHARACTERISTICS:** +- Runtime errors are USER-INTERACTION DRIVEN - they only appear when users interact with the app +- Errors may be a few seconds stale (not real-time) +- Errors persist in storage until explicitly cleared + +**BEST PRACTICE WORKFLOW:** +1. Call this tool to see what runtime errors exist +2. If you make fixes and deploy changes (deploy_preview) +3. Use wait(20-30) to allow time for user interaction +4. Call get_runtime_errors again to verify errors are resolved + +**When to use:** +- ✅ To see what runtime errors users have encountered +- ✅ After deploying fixes to verify issues are resolved +- ✅ To understand error patterns in the application + +**When NOT to use:** +- ❌ Immediately after deploy (errors need user interaction to generate) +- ❌ In rapid succession (errors update on user interaction, not continuously)`, + parameters: { + type: 'object', + properties: {}, + required: [], + }, + }, + implementation: async (_args?) => { + try { + logger.info('Fetching runtime errors from sandbox'); + + const errors = await agent.fetchRuntimeErrors(true); + + return { + errors: errors || [] + }; + } catch (error) { + return { + error: + error instanceof Error + ? `Failed to get runtime errors: ${error.message}` + : 'Unknown error occurred while fetching runtime errors', + }; + } + }, + }; +} diff --git a/worker/agents/tools/toolkit/regenerate-file.ts b/worker/agents/tools/toolkit/regenerate-file.ts index b9f01888..7afca870 100644 --- a/worker/agents/tools/toolkit/regenerate-file.ts +++ b/worker/agents/tools/toolkit/regenerate-file.ts @@ -8,7 +8,7 @@ export type RegenerateFileArgs = { }; export type RegenerateFileResult = - | { path: string; updatedPreview: string } + | { path: string; diff: string } | ErrorResult; export function createRegenerateFileTool( @@ -20,7 +20,9 @@ export function createRegenerateFileTool( function: { name: 'regenerate_file', description: - 'Apply a surgical fix to a file (search/replace style) using internal regeneration operation, then persist changes.', + `Autonomous AI agent that applies surgical fixes to code files. Takes file path and array of specific issues to fix. Returns diff showing changes made. + +CRITICAL: Provide detailed, specific issues - not vague descriptions. See system prompt for full usage guide. These would be implemented by an independent LLM AI agent`, parameters: { type: 'object', properties: { diff --git a/worker/agents/tools/toolkit/wait-for-generation.ts b/worker/agents/tools/toolkit/wait-for-generation.ts new file mode 100644 index 00000000..d34224c7 --- /dev/null +++ b/worker/agents/tools/toolkit/wait-for-generation.ts @@ -0,0 +1,43 @@ +import { ToolDefinition } from '../types'; +import { StructuredLogger } from '../../../logger'; +import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; + +export function createWaitForGenerationTool( + agent: CodingAgentInterface, + logger: StructuredLogger +): ToolDefinition, { status: string } | { error: string }> { + return { + type: 'function', + function: { + name: 'wait_for_generation', + description: + 'Wait for code generation to complete. Use when deep_debug returns GENERATION_IN_PROGRESS error. Returns immediately if no generation is running.', + parameters: { + type: 'object', + properties: {}, + required: [], + }, + }, + implementation: async () => { + try { + if (agent.isCodeGenerating()) { + logger.info('Waiting for code generation to complete...'); + await agent.waitForGeneration(); + logger.info('Code generation completed'); + return { status: 'Generation completed' }; + } else { + logger.info('No code generation in progress'); + return { status: 'No generation was running' }; + } + } catch (error) { + logger.error('Error waiting for generation', error); + return { + error: + error instanceof Error + ? `Failed to wait for generation: ${error.message}` + : 'Unknown error while waiting for generation', + }; + } + }, + }; +} diff --git a/worker/agents/tools/toolkit/wait.ts b/worker/agents/tools/toolkit/wait.ts new file mode 100644 index 00000000..39233981 --- /dev/null +++ b/worker/agents/tools/toolkit/wait.ts @@ -0,0 +1,48 @@ +import { ToolDefinition } from '../types'; +import { StructuredLogger } from '../../../logger'; + +type WaitArgs = { + seconds: number; + reason?: string; +}; + +type WaitResult = { message: string }; + +export function createWaitTool( + logger: StructuredLogger, +): ToolDefinition { + return { + type: 'function' as const, + function: { + name: 'wait', + description: + 'Wait/sleep for a specified number of seconds. Use this after deploying changes when you need the user to interact with the app before checking logs. Typical usage: wait 15-30 seconds after deploy_preview to allow time for user interaction.', + parameters: { + type: 'object', + properties: { + seconds: { + type: 'number', + description: 'Number of seconds to wait (typically 15-30 for user interaction)', + }, + reason: { + type: 'string', + description: 'Optional: why you are waiting (e.g., "Waiting for user to interact with app")', + }, + }, + required: ['seconds'], + }, + }, + implementation: async ({ seconds, reason }) => { + const waitMs = Math.min(Math.max(seconds * 1000, 1000), 60000); // Clamp between 1-60 seconds + const actualSeconds = waitMs / 1000; + + logger.info('Waiting', { seconds: actualSeconds, reason }); + + await new Promise(resolve => setTimeout(resolve, waitMs)); + + return { + message: `Waited ${actualSeconds} seconds${reason ? `: ${reason}` : ''}`, + }; + }, + }; +} From b0afd1533f736976367e8a3585a7d260d76fd8c4 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 16:31:30 -0400 Subject: [PATCH 018/150] fix: add missing await for async getFullState call in getAgentState function --- worker/agents/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker/agents/index.ts b/worker/agents/index.ts index b977a84f..e5b445d3 100644 --- a/worker/agents/index.ts +++ b/worker/agents/index.ts @@ -43,7 +43,7 @@ export async function getAgentStub(env: Env, agentId: string, searchInOtherJuris export async function getAgentState(env: Env, agentId: string, searchInOtherJurisdictions: boolean = false, logger: StructuredLogger) : Promise { const agentInstance = await getAgentStub(env, agentId, searchInOtherJurisdictions, logger); - return agentInstance.getFullState() as CodeGenState; + return await agentInstance.getFullState() as CodeGenState; } export async function cloneAgent(env: Env, agentId: string, logger: StructuredLogger) : Promise<{newAgentId: string, newAgent: DurableObjectStub}> { From 45ac9105ad920e36cd7b284a050a414c1913a2a6 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 16:32:06 -0400 Subject: [PATCH 019/150] feat: add previous debug session context and improve error handling in debugging tools --- worker/agents/assistants/codeDebugger.ts | 35 ++++++++++++++++--- .../operations/UserConversationProcessor.ts | 9 ++++- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/worker/agents/assistants/codeDebugger.ts b/worker/agents/assistants/codeDebugger.ts index f66f58c5..9b51deb4 100644 --- a/worker/agents/assistants/codeDebugger.ts +++ b/worker/agents/assistants/codeDebugger.ts @@ -247,16 +247,36 @@ You're done when: - No play-by-play narration - just execute - Quality through internal reasoning, not verbose output -The goal is working code, verified through evidence. Think internally, act decisively.`; +The goal is working code, verified through evidence. Think internally, act decisively. + + +The most important class of errors is the "Maximum update depth exceeded" error which you definitely need to identify and fix. +${PROMPT_UTILS.REACT_RENDER_LOOP_PREVENTION} + +${PROMPT_UTILS.COMMON_DEP_DOCUMENTATION} +`; const USER_PROMPT = ( issue: string, fileSummaries: string, templateInfo?: string, - runtimeErrors?: string + runtimeErrors?: string, + previousTranscript?: string ) => `## Debugging Task **Issue to resolve:** ${issue} +${previousTranscript ? `## Previous Debug Session Context +A previous debug session was completed. Here's what was done: + +${previousTranscript} + +**IMPORTANT:** Use this context to: +- Avoid redoing work already completed +- Build on previous fixes +- Reference previous findings if relevant +- Continue from where the last session left off if this is a related issue +` : ''} + ## Project Context Below is metadata about the codebase. Use this to orient yourself, but read actual file contents when you need details. @@ -280,7 +300,7 @@ These runtime errors were captured from the sandbox. Note that they may be a few ${runtimeErrors}` : ''} ## Your Mission -Diagnose and fix this issue. +Diagnose and fix all user issues. **Approach:** - Think deeply internally (you have high reasoning capability) @@ -311,6 +331,7 @@ export type DebugSession = { export type DebugInputs = { issue: string; + previousTranscript?: string; }; function summarizeFiles(files: FileState[], max = 120): string { @@ -405,7 +426,13 @@ If you're genuinely stuck after trying 3 different approaches, honestly report: const system = createSystemMessage(SYSTEM_PROMPT); const user = createUserMessage( - USER_PROMPT(inputs.issue, fileSummaries, templateInfo, session.runtimeErrors ? PROMPT_UTILS.serializeErrors(session.runtimeErrors) : undefined) + USER_PROMPT( + inputs.issue, + fileSummaries, + templateInfo, + session.runtimeErrors ? PROMPT_UTILS.serializeErrors(session.runtimeErrors) : undefined, + inputs.previousTranscript + ) ); const messages: Message[] = this.save([system, user]); diff --git a/worker/agents/operations/UserConversationProcessor.ts b/worker/agents/operations/UserConversationProcessor.ts index d55a6f2a..91f15253 100644 --- a/worker/agents/operations/UserConversationProcessor.ts +++ b/worker/agents/operations/UserConversationProcessor.ts @@ -109,6 +109,7 @@ const SYSTEM_PROMPT = `You are Orange, the conversational AI interface for Cloud - get_logs: Fetch unread application logs from the sandbox to diagnose runtime issues. - deep_debug: Autonomous debugging assistant that investigates errors, reads files, runs commands, and applies targeted fixes. Use when users report bugs/errors that need immediate investigation and fixing. This transfers control to a specialized debugging agent. - wait_for_generation: Wait for code generation to complete. Use when deep_debug returns GENERATION_IN_PROGRESS error. + - wait_for_debug: Wait for current debug session to complete. Use when deep_debug returns DEBUG_IN_PROGRESS error. - deploy_preview: Redeploy or restart the preview when the user asks to deploy or the preview is blank/looping. - clear_conversation: Clear the current chat history for this session. - rename_project: Rename the project (lowercase letters, numbers, hyphens, underscores; 3-50 chars). @@ -142,6 +143,12 @@ When you call deep_debug, it runs to completion and returns a transcript. The us 3. Retry deep_debug 4. If it fails again, report the issue +**CRITICAL - If deep_debug returns DEBUG_IN_PROGRESS error:** +1. Tell user: "A debug session is running. Let me wait for it to complete..." +2. Call wait_for_debug +3. Retry deep_debug (it will have context from previous session) +4. If it fails again, report the issue + **Option 2 - For feature requests or non-urgent fixes:** Queue the request via queue_request - the development agent will address it in the next phase. Then tell the user: "I'll fix this issue in the next phase or two." @@ -213,6 +220,7 @@ We have also recently added support for image inputs in beta. User can guide app - Sometimes your request might be lost. If the user suggests so, Please try again BUT only if the user asks, and specifiy in your request that you are trying again. - Always be concise, direct, to the point and brief to the user. You are a man of few words. Dont talk more than what's necessary to the user. - For persistent problems, actively use \`get_logs\` tool to fetch the latest server logs. +- deep_debug tool is especially well suited for debugging and fixing issues like maximum update depth exceeded errors, or website not working/loading. Use it primarily for such issues You can also execute multiple tools in a sequence, for example, to search the web for an image, and then sending the image url to the queue_request tool to queue up the changes. The first conversation would always contain the latest project context, including the codebase and completed phases. Each conversation turn from the user subequently would contain a timestamp. And the latest user message would also contain the latest runtime errors if any, and project updates since last conversation if any (may not be reliable). @@ -322,7 +330,6 @@ export class UserConversationProcessor extends AgentOperation inputs.conversationResponseCallback(message, aiConversationId, true), toolCallRenderer, ).map(td => ({ ...td, From e60694f6334b2c05acd1c7147650fc88c9adbd61 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 16:32:44 -0400 Subject: [PATCH 020/150] feat: add deep debug session management with state persistence and wait tools --- worker/agents/core/simpleGeneratorAgent.ts | 82 ++++++++++++++++++- worker/agents/core/state.ts | 2 + worker/agents/core/types.ts | 9 +- .../services/implementations/CodingAgent.ts | 18 ++++ .../services/interfaces/ICodingAgent.ts | 12 +++ worker/agents/tools/customTools.ts | 6 +- worker/agents/tools/toolkit/deep-debugger.ts | 54 +++++------- worker/agents/tools/toolkit/wait-for-debug.ts | 43 ++++++++++ 8 files changed, 189 insertions(+), 37 deletions(-) create mode 100644 worker/agents/tools/toolkit/wait-for-debug.ts diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index e6b0b7b8..6d717109 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -14,7 +14,7 @@ import { MAX_DEPLOYMENT_RETRIES, PREVIEW_EXPIRED_ERROR, WebSocketMessageResponse import { broadcastToConnections, handleWebSocketClose, handleWebSocketMessage } from './websocket'; import { createObjectLogger, StructuredLogger } from '../../logger'; import { ProjectSetupAssistant } from '../assistants/projectsetup'; -import { UserConversationProcessor } from '../operations/UserConversationProcessor'; +import { UserConversationProcessor, RenderToolCall } from '../operations/UserConversationProcessor'; import { FileManager } from '../services/implementations/FileManager'; import { StateManager } from '../services/implementations/StateManager'; // import { WebSocketBroadcaster } from '../services/implementations/WebSocketBroadcaster'; @@ -47,6 +47,9 @@ import { CodingAgentInterface } from '../services/implementations/CodingAgent'; import { generateAppProxyToken, generateAppProxyUrl } from 'worker/services/aigateway-proxy/controller'; import { ImageType, uploadImage } from 'worker/utils/images'; import { ConversationMessage, ConversationState } from '../inferutils/common'; +import { DeepCodeDebugger } from '../assistants/codeDebugger'; +import { IdGenerator } from '../utils/idGenerator'; +import { DeepDebugResult } from './types'; interface WebhookPayload { event: { @@ -106,6 +109,7 @@ export class SimpleCodeGeneratorAgent extends Agent { // These are temporary and will be lost if the DO is evicted private pendingUserImages: ProcessedImageAttachment[] = [] private generationPromise: Promise | null = null; + private deepDebugPromise: Promise<{ transcript: string } | { error: string }> | null = null; protected operations: Operations = { codeReview: new CodeReviewOperation(), @@ -168,6 +172,7 @@ export class SimpleCodeGeneratorAgent extends Agent { shouldBeGenerating: false, reviewingInitiated: false, projectUpdatesAccumulator: [], + lastDeepDebugTranscript: null, }; /* @@ -844,6 +849,66 @@ export class SimpleCodeGeneratorAgent extends Agent { return CurrentDevState.REVIEWING; } + async executeDeepDebug( + issue: string, + focusPaths?: string[], + toolRenderer?: RenderToolCall + ): Promise { + + const debugPromise = (async () => { + try { + const previousTranscript = this.state.lastDeepDebugTranscript ?? undefined; + const operationOptions = this.getOperationOptions(); + const filesIndex = operationOptions.context.allFiles + .filter((f) => + !focusPaths?.length || + focusPaths.some((p) => f.filePath.includes(p)), + ); + + const runtimeErrors = await this.fetchRuntimeErrors(true); + + const dbg = new DeepCodeDebugger( + operationOptions.env, + operationOptions.inferenceContext, + ); + // Create streaming callback using broadcast mechanism + const streamCallback = (chunk: string) => { + this.broadcast(WebSocketMessageResponses.CONVERSATION_RESPONSE, { + message: chunk, + conversationId: `deep-debug-${IdGenerator.generateConversationId()}`, + isStreaming: true, + }); + }; + + const out = await dbg.run( + { issue, previousTranscript }, + { filesIndex, agent: this.codingAgent, runtimeErrors }, + streamCallback, + toolRenderer, + ); + + // Save transcript for next session + this.setState({ + ...this.state, + lastDeepDebugTranscript: out, + }); + + return { success: true as const, transcript: out }; + } catch (e) { + this.logger().error('Deep debugger failed', e); + return { success: false as const, error: `Deep debugger failed: ${String(e)}` }; + } finally{ + // Clear promise after completion + this.deepDebugPromise = null; + } + })(); + + // Store promise before awaiting + this.deepDebugPromise = debugPromise; + + return await debugPromise; + } + /** * Generate next phase with user context (suggestions and images) */ @@ -2159,6 +2224,21 @@ export class SimpleCodeGeneratorAgent extends Agent { } } + isDeepDebugging(): boolean { + return this.deepDebugPromise !== null; + } + + async waitForDeepDebug(): Promise { + if (this.deepDebugPromise) { + try { + await this.deepDebugPromise; + this.logger().info("Deep debug session completed successfully"); + } catch (error) { + this.logger().error("Error during deep debug session:", error); + } + } + } + async onMessage(connection: Connection, message: string): Promise { handleWebSocketMessage(this, connection, message); } diff --git a/worker/agents/core/state.ts b/worker/agents/core/state.ts index 54d6ac9a..669dea8a 100644 --- a/worker/agents/core/state.ts +++ b/worker/agents/core/state.ts @@ -58,4 +58,6 @@ export interface CodeGenState { conversationMessages: ConversationMessage[]; projectUpdatesAccumulator: string[]; inferenceContext: InferenceContext; + + lastDeepDebugTranscript: string | null; } diff --git a/worker/agents/core/types.ts b/worker/agents/core/types.ts index 709b6f47..91e5412b 100644 --- a/worker/agents/core/types.ts +++ b/worker/agents/core/types.ts @@ -57,4 +57,11 @@ export interface PhaseExecutionResult { result?: PhaseConceptType; userSuggestions?: string[]; userContext?: UserContext; -} \ No newline at end of file +} + +/** + * Result type for deep debug operations + */ +export type DeepDebugResult = + | { success: true; transcript: string } + | { success: false; error: string }; \ No newline at end of file diff --git a/worker/agents/services/implementations/CodingAgent.ts b/worker/agents/services/implementations/CodingAgent.ts index 1bca670c..a606c70d 100644 --- a/worker/agents/services/implementations/CodingAgent.ts +++ b/worker/agents/services/implementations/CodingAgent.ts @@ -3,6 +3,8 @@ import { Blueprint } from "worker/agents/schemas"; import { ExecuteCommandsResponse, StaticAnalysisResponse, RuntimeError } from "worker/services/sandbox/sandboxTypes"; import { ICodingAgent } from "../interfaces/ICodingAgent"; import { OperationOptions } from "worker/agents/operations/common"; +import { RenderToolCall } from "worker/agents/operations/UserConversationProcessor"; +import { DeepDebugResult } from "worker/agents/core/types"; /* * CodingAgentInterface - stub for passing to tool calls @@ -84,4 +86,20 @@ export class CodingAgentInterface { waitForGeneration(): Promise { return this.agentStub.waitForGeneration(); } + + isDeepDebugging(): boolean { + return this.agentStub.isDeepDebugging(); + } + + waitForDeepDebug(): Promise { + return this.agentStub.waitForDeepDebug(); + } + + executeDeepDebug( + issue: string, + focusPaths?: string[], + toolRenderer?: RenderToolCall + ): Promise { + return this.agentStub.executeDeepDebug(issue, focusPaths, toolRenderer); + } } diff --git a/worker/agents/services/interfaces/ICodingAgent.ts b/worker/agents/services/interfaces/ICodingAgent.ts index 3ecafb52..385eb56a 100644 --- a/worker/agents/services/interfaces/ICodingAgent.ts +++ b/worker/agents/services/interfaces/ICodingAgent.ts @@ -3,6 +3,8 @@ import { BaseSandboxService } from "worker/services/sandbox/BaseSandboxService"; import { ExecuteCommandsResponse, PreviewType, StaticAnalysisResponse, RuntimeError } from "worker/services/sandbox/sandboxTypes"; import { ProcessedImageAttachment } from "worker/types/image-attachment"; import { OperationOptions } from "worker/agents/operations/common"; +import { RenderToolCall } from "worker/agents/operations/UserConversationProcessor"; +import { DeepDebugResult } from "worker/agents/core/types"; export abstract class ICodingAgent { abstract getSandboxServiceClient(): BaseSandboxService; @@ -36,4 +38,14 @@ export abstract class ICodingAgent { abstract isCodeGenerating(): boolean; abstract waitForGeneration(): Promise; + + abstract isDeepDebugging(): boolean; + + abstract waitForDeepDebug(): Promise; + + abstract executeDeepDebug( + issue: string, + focusPaths?: string[], + toolRenderer?: RenderToolCall + ): Promise; } diff --git a/worker/agents/tools/customTools.ts b/worker/agents/tools/customTools.ts index 4ab2872a..9993dbbd 100644 --- a/worker/agents/tools/customTools.ts +++ b/worker/agents/tools/customTools.ts @@ -18,7 +18,7 @@ import { createRegenerateFileTool } from './toolkit/regenerate-file'; import { createWaitTool } from './toolkit/wait'; import { createGetRuntimeErrorsTool } from './toolkit/get-runtime-errors'; import { createWaitForGenerationTool } from './toolkit/wait-for-generation'; -import { RuntimeError } from 'worker/services/sandbox/sandboxTypes'; +import { createWaitForDebugTool } from './toolkit/wait-for-debug'; export async function executeToolWithDefinition( toolDef: ToolDefinition, @@ -37,7 +37,6 @@ export async function executeToolWithDefinition( export function buildTools( agent: CodingAgentInterface, logger: StructuredLogger, - streamCb?: (message: string) => void, toolRenderer?: RenderToolCall, ): ToolDefinition[] { return [ @@ -47,10 +46,11 @@ export function buildTools( createGetLogsTool(agent, logger), createDeployPreviewTool(agent, logger), createWaitForGenerationTool(agent, logger), + createWaitForDebugTool(agent, logger), createRenameProjectTool(agent, logger), createAlterBlueprintTool(agent, logger), // Deep autonomous debugging assistant tool - createDeepDebuggerTool(agent, logger, streamCb, toolRenderer), + createDeepDebuggerTool(agent, logger, toolRenderer), ]; } diff --git a/worker/agents/tools/toolkit/deep-debugger.ts b/worker/agents/tools/toolkit/deep-debugger.ts index e2ba6dd1..9f3e0c67 100644 --- a/worker/agents/tools/toolkit/deep-debugger.ts +++ b/worker/agents/tools/toolkit/deep-debugger.ts @@ -1,13 +1,11 @@ import { ToolDefinition } from '../types'; import { StructuredLogger } from '../../../logger'; import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; -import { DeepCodeDebugger } from 'worker/agents/assistants/codeDebugger'; import { RenderToolCall } from '../../operations/UserConversationProcessor'; export function createDeepDebuggerTool( agent: CodingAgentInterface, logger: StructuredLogger, - streamCb?: (message: string) => void, toolRenderer?: RenderToolCall, ): ToolDefinition< { issue: string; focus_paths?: string[] }, @@ -29,38 +27,30 @@ export function createDeepDebuggerTool( }, }, implementation: async ({ issue, focus_paths }: { issue: string; focus_paths?: string[] }) => { - try { - // Check if code generation is in progress - if (agent.isCodeGenerating()) { - logger.warn('Cannot start debugging: Code generation in progress'); - return { - error: 'GENERATION_IN_PROGRESS: Code generation is currently running. Use wait_for_generation tool, then retry deep_debug.' - }; - } - - const operationOptions = agent.getOperationOptions(); - const filesIndex = operationOptions.context.allFiles - .filter((f) => - !focus_paths?.length || - focus_paths.some((p) => f.filePath.includes(p)), - ); + // Check if code generation is in progress + if (agent.isCodeGenerating()) { + logger.warn('Cannot start debugging: Code generation in progress'); + return { + error: 'GENERATION_IN_PROGRESS: Code generation is currently running. Use wait_for_generation tool, then retry deep_debug.' + }; + } - const runtimeErrors = await agent.fetchRuntimeErrors(true); + // Check if another debug session is running + if (agent.isDeepDebugging()) { + logger.warn('Cannot start debugging: Another debug session in progress'); + return { + error: 'DEBUG_IN_PROGRESS: Another debug session is currently running. Use wait_for_debug tool, then retry deep_debug.' + }; + } - const dbg = new DeepCodeDebugger( - operationOptions.env, - operationOptions.inferenceContext, - ); - const out = await dbg.run( - { issue }, - { filesIndex, agent, runtimeErrors }, - streamCb ? (chunk) => streamCb(chunk) : undefined, - toolRenderer, - ); - return { transcript: out }; - } catch (e) { - logger.error('Deep debugger failed', e); - return { error: `Deep debugger failed: ${String(e)}` }; + // Execute debug session - agent handles all logic internally + const result = await agent.executeDeepDebug(issue, focus_paths, toolRenderer); + + // Convert discriminated union to tool response format + if (result.success) { + return { transcript: result.transcript }; + } else { + return { error: result.error }; } }, }; diff --git a/worker/agents/tools/toolkit/wait-for-debug.ts b/worker/agents/tools/toolkit/wait-for-debug.ts new file mode 100644 index 00000000..2852ea5a --- /dev/null +++ b/worker/agents/tools/toolkit/wait-for-debug.ts @@ -0,0 +1,43 @@ +import { ToolDefinition } from '../types'; +import { StructuredLogger } from '../../../logger'; +import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; + +export function createWaitForDebugTool( + agent: CodingAgentInterface, + logger: StructuredLogger +): ToolDefinition, { status: string } | { error: string }> { + return { + type: 'function', + function: { + name: 'wait_for_debug', + description: + 'Wait for the current debug session to complete. Use when deep_debug returns DEBUG_IN_PROGRESS error. Returns immediately if no debug session is running.', + parameters: { + type: 'object', + properties: {}, + required: [], + }, + }, + implementation: async () => { + try { + if (agent.isDeepDebugging()) { + logger.info('Waiting for debug session to complete...'); + await agent.waitForDeepDebug(); + logger.info('Debug session completed'); + return { status: 'Debug session completed' }; + } else { + logger.info('No debug session in progress'); + return { status: 'No debug session was running' }; + } + } catch (error) { + logger.error('Error waiting for debug session', error); + return { + error: + error instanceof Error + ? `Failed to wait for debug session: ${error.message}` + : 'Unknown error while waiting for debug session', + }; + } + }, + }; +} From 0256634a6a042d7136a6b9bcda80ea92a8d1478c Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 17:08:43 -0400 Subject: [PATCH 021/150] feat: implement message deduplication to prevent duplicate assistant responses --- src/routes/chat/utils/deduplicate-messages.ts | 63 +++++++++++++++++++ .../chat/utils/handle-websocket-message.ts | 25 ++++++-- worker/agents/inferutils/core.ts | 24 ++++++- .../operations/UserConversationProcessor.ts | 33 +++++++++- 4 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 src/routes/chat/utils/deduplicate-messages.ts diff --git a/src/routes/chat/utils/deduplicate-messages.ts b/src/routes/chat/utils/deduplicate-messages.ts new file mode 100644 index 00000000..fa249487 --- /dev/null +++ b/src/routes/chat/utils/deduplicate-messages.ts @@ -0,0 +1,63 @@ +import type { ChatMessage } from './message-helpers'; + +/** + * Deduplicates consecutive assistant messages with identical content. + * + * This handles cases where the backend sends duplicate responses after tool execution, + * even when tool messages appear between them. + * + * Algorithm: + * - Keeps all non-assistant messages + * - For each assistant message, checks if the last assistant (not necessarily adjacent) has identical content + * - If duplicate found, skips the current message + * + * @param messages - Array of chat messages to deduplicate + * @returns Deduplicated array of messages + */ +export function deduplicateMessages(messages: readonly ChatMessage[]): ChatMessage[] { + if (messages.length === 0) return []; + + const result: ChatMessage[] = []; + let lastAssistantContent: string | null = null; + + for (const msg of messages) { + if (msg.role !== 'assistant') { + // Keep all non-assistant messages (user, tool, etc.) + result.push(msg); + continue; + } + + // For assistant messages, check against last assistant content + if (lastAssistantContent !== null && msg.content === lastAssistantContent) { + // Skip this duplicate + continue; + } + + // Not a duplicate - keep it and update last content + result.push(msg); + lastAssistantContent = msg.content; + } + + return result; +} + +/** + * Check if a new assistant message would be a duplicate of the last assistant message. + * Used for live streaming to prevent adding duplicates. + * + * @param messages - Current messages array + * @param newContent - Content of the new assistant message + * @returns true if this would be a duplicate, false otherwise + */ +export function isAssistantMessageDuplicate( + messages: readonly ChatMessage[], + newContent: string +): boolean { + // Find the last assistant message + for (let i = messages.length - 1; i >= 0; i--) { + if (messages[i].role === 'assistant') { + return messages[i].content === newContent; + } + } + return false; // No previous assistant message found +} diff --git a/src/routes/chat/utils/handle-websocket-message.ts b/src/routes/chat/utils/handle-websocket-message.ts index b3ffb9f7..f47508f3 100644 --- a/src/routes/chat/utils/handle-websocket-message.ts +++ b/src/routes/chat/utils/handle-websocket-message.ts @@ -1,5 +1,6 @@ import type { WebSocket } from 'partysocket'; import type { WebSocketMessage, BlueprintType, ConversationMessage } from '@/api-types'; +import { deduplicateMessages, isAssistantMessageDuplicate } from './deduplicate-messages'; import { logger } from '@/utils/logger'; import { getFileType } from '@/utils/string'; import { getPreviewUrl } from '@/lib/utils'; @@ -253,7 +254,7 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { case 'conversation_state': { const { state } = message; const history: ReadonlyArray = state?.runningHistory ?? []; - logger.debug('Received conversation_state with messages:', history.length); + logger.debug('Received conversation_state with messages:', history.length, 'state:', state); const restoredMessages: ChatMessage[] = []; let currentAssistant: ChatMessage | null = null; @@ -279,7 +280,8 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { ? 'previous history was compacted' : (text || ''); - if (currentAssistant) { + // Only merge if same conversationId (continuation), otherwise create new + if (currentAssistant && currentAssistant.conversationId === msg.conversationId) { if (content) { currentAssistant.content += (currentAssistant.content ? '\n\n' : '') + content; } @@ -306,7 +308,10 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { } if (restoredMessages.length > 0) { - logger.debug('Merging conversation_state with', restoredMessages.length, 'messages'); + // Deduplicate assistant messages with identical content (even if separated by tool messages) + const deduplicated = deduplicateMessages(restoredMessages); + + logger.debug('Merging conversation_state with', deduplicated.length, 'messages (', restoredMessages.length - deduplicated.length, 'duplicates removed)'); setMessages(prev => { const hasFetching = prev.some(m => m.role === 'assistant' && m.conversationId === 'fetching-chat'); if (hasFetching) { @@ -314,9 +319,9 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { name: 'fetching your latest conversations', status: 'success' }); - return [...next, ...restoredMessages]; + return [...next, ...deduplicated]; } - return restoredMessages; + return deduplicated; }); } break; @@ -693,7 +698,15 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { setMessages(prev => { const idx = prev.findIndex(m => m.role === 'assistant' && m.conversationId === conversationId); if (idx !== -1) return prev.map((m, i) => i === idx ? { ...m, content: (isArchive ? placeholder : message.message) } : m); - return [...prev, createAIMessage(conversationId, isArchive ? placeholder : message.message)]; + + // Deduplicate: Don't add if last assistant message has identical content + const newContent = isArchive ? placeholder : message.message; + if (isAssistantMessageDuplicate(prev, newContent)) { + logger.debug('Skipping duplicate assistant message'); + return prev; // Skip duplicate + } + + return [...prev, createAIMessage(conversationId, newContent)]; }); break; } diff --git a/worker/agents/inferutils/core.ts b/worker/agents/inferutils/core.ts index b849f0d2..c20c29ce 100644 --- a/worker/agents/inferutils/core.ts +++ b/worker/agents/inferutils/core.ts @@ -700,8 +700,28 @@ export async function infer({ depth: newDepth }; - const executedCallsWithResults = executedToolCalls.filter(result => result.result); - console.log(`Tool calling depth: ${newDepth}/${MAX_TOOL_CALLING_DEPTH}`); + // Filter out tools with empty/meaningless results to avoid duplicate responses + const executedCallsWithResults = executedToolCalls.filter(result => { + if (!result.result) return false; + + // Check if result is effectively empty + const stringResult = typeof result.result === 'string' + ? result.result + : JSON.stringify(result.result); + + // Skip if result is null, empty string, "done", or just whitespace + if (!stringResult || + stringResult === 'null' || + stringResult === '""' || + stringResult === 'done' || + stringResult.trim() === '') { + console.log(`[TOOL_CALL_SKIP] Skipping redundant LLM call for tool '${result.name}' with empty result: ${stringResult}`); + return false; + } + + return true; + }); + console.log(`Tool calling depth: ${newDepth}/${MAX_TOOL_CALLING_DEPTH}, calls with meaningful results: ${executedCallsWithResults.length}/${executedToolCalls.length}`); if (executedCallsWithResults.length) { if (schema && schemaName) { diff --git a/worker/agents/operations/UserConversationProcessor.ts b/worker/agents/operations/UserConversationProcessor.ts index 91f15253..c39ac7b6 100644 --- a/worker/agents/operations/UserConversationProcessor.ts +++ b/worker/agents/operations/UserConversationProcessor.ts @@ -154,6 +154,25 @@ Queue the request via queue_request - the development agent will address it in t **DO NOT try to solve bugs yourself!** Use deep_debug for immediate fixes or queue_request for later implementation. +## CRITICAL - After Tool Execution: +When a tool completes execution, you should respond based on what the tool returned: + +**If tool returns meaningful data** (logs, search results, transcripts, etc.): +- Synthesize and share the information with the user +- Add new insights based on the tool's output + +**If tool returns empty/minimal result** (null, "done", empty string): +- The tool succeeded silently - you already told the user what you're doing +- DO NOT repeat your previous message +- Either: + - Say nothing more (system will show tool completion) + - OR add a brief confirmation: "✓" or "Done" +- NEVER repeat your entire previous explanation + +**Examples:** +❌ BAD: User asks for fix → You say "I'll queue that" + call queue_request → Tool returns "done" → You say "I'll queue that" again +✅ GOOD: User asks for fix → You say "I'll queue that" + call queue_request → Tool returns "done" → You say nothing OR "✓" + ## How the AI vibecoding platform itself works: - Its a simple state machine: - User writes an initial prompt describing what app they want @@ -412,7 +431,19 @@ export class UserConversationProcessor extends AgentOperation ({ ...message, conversationId: IdGenerator.generateConversationId() })) ); } - messages.push({...createAssistantMessage(result.string), conversationId: IdGenerator.generateConversationId()}); + + // Check if final response is duplicate of last assistant message in tool context + const finalResponse = createAssistantMessage(result.string); + const lastToolContextMessage = result.toolCallContext?.messages?.[result.toolCallContext.messages.length - 1]; + const isDuplicate = lastToolContextMessage?.role === 'assistant' && + lastToolContextMessage?.content === finalResponse.content; + + if (!isDuplicate) { + messages.push({...finalResponse, conversationId: IdGenerator.generateConversationId()}); + logger.info("Added final assistant response to history"); + } else { + logger.info("Skipped duplicate final assistant response"); + } // Derive compacted running history for storage using stable IDs (no re-compaction) const originalRunning = conversationState.runningHistory; From 8375f5d087c9e1143b371ae9296714d847297581 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 17:25:56 -0400 Subject: [PATCH 022/150] refactor: improve deep debugger conversation handling and remove loop blocking --- worker/agents/assistants/codeDebugger.ts | 23 ++++++++++++------- worker/agents/core/simpleGeneratorAgent.ts | 10 ++++---- .../operations/UserConversationProcessor.ts | 3 +-- .../services/implementations/CodingAgent.ts | 4 +--- .../services/interfaces/ICodingAgent.ts | 2 -- worker/agents/tools/customTools.ts | 3 +-- worker/agents/tools/toolkit/deep-debugger.ts | 4 +--- 7 files changed, 23 insertions(+), 26 deletions(-) diff --git a/worker/agents/assistants/codeDebugger.ts b/worker/agents/assistants/codeDebugger.ts index 9b51deb4..ef10f5a3 100644 --- a/worker/agents/assistants/codeDebugger.ts +++ b/worker/agents/assistants/codeDebugger.ts @@ -251,6 +251,7 @@ The goal is working code, verified through evidence. Think internally, act decis The most important class of errors is the "Maximum update depth exceeded" error which you definitely need to identify and fix. +Here are some important guidelines for identifying such issues and preventing them: ${PROMPT_UTILS.REACT_RENDER_LOOP_PREVENTION} ${PROMPT_UTILS.COMMON_DEP_DOCUMENTATION} @@ -350,11 +351,14 @@ function summarizeFiles(files: FileState[], max = 120): string { export class DeepCodeDebugger extends Assistant { logger = createObjectLogger(this, 'DeepCodeDebugger'); modelConfigOverride?: ModelConfig; + private loopDetection: LoopDetectionState = { recentCalls: [], repetitionWarnings: 0, }; + private conversationId: string; + constructor( env: Env, inferenceContext: InferenceContext, @@ -362,6 +366,11 @@ export class DeepCodeDebugger extends Assistant { ) { super(env, inferenceContext); this.modelConfigOverride = modelConfigOverride; + this.conversationId = `deep-debug-${IdGenerator.generateConversationId()}`; + } + + getConversationId(): string { + return this.conversationId; } private detectRepetition(toolName: string, args: Record): boolean { @@ -389,13 +398,11 @@ export class DeepCodeDebugger extends Assistant { this.loopDetection.repetitionWarnings++; const warningMessage = ` -⚠️ CRITICAL: REPETITION DETECTED - TOOL CALL BLOCKED +⚠️ CRITICAL: REPETITION DETECTED You just attempted to execute "${toolName}" with identical arguments for the ${this.loopDetection.repetitionWarnings}th time. -This tool call was BLOCKED to prevent an infinite loop. - -REQUIRED ACTIONS: +RECOMMENDED ACTIONS: 1. If your task is complete, state "TASK_COMPLETE: [summary]" and STOP 2. If not complete, try a DIFFERENT approach: - Use different tools @@ -448,10 +455,10 @@ If you're genuinely stuck after trying 3 different approaches, honestly report: this.logger.warn(`Loop detected for tool: ${tool.function.name}`); this.injectLoopWarning(tool.function.name); - // CRITICAL: Block execution to prevent infinite loops - return { - error: `Loop detected: You've called ${tool.function.name} with the same arguments multiple times. Try a different approach or stop if the task is complete.` - }; + // // CRITICAL: Block execution to prevent infinite loops + // return { + // error: `Loop detected: You've called ${tool.function.name} with the same arguments multiple times. Try a different approach or stop if the task is complete.` + // }; } // Only execute if no loop detected diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index 6d717109..8e22fa00 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -14,7 +14,7 @@ import { MAX_DEPLOYMENT_RETRIES, PREVIEW_EXPIRED_ERROR, WebSocketMessageResponse import { broadcastToConnections, handleWebSocketClose, handleWebSocketMessage } from './websocket'; import { createObjectLogger, StructuredLogger } from '../../logger'; import { ProjectSetupAssistant } from '../assistants/projectsetup'; -import { UserConversationProcessor, RenderToolCall } from '../operations/UserConversationProcessor'; +import { UserConversationProcessor, buildToolCallRenderer } from '../operations/UserConversationProcessor'; import { FileManager } from '../services/implementations/FileManager'; import { StateManager } from '../services/implementations/StateManager'; // import { WebSocketBroadcaster } from '../services/implementations/WebSocketBroadcaster'; @@ -48,7 +48,6 @@ import { generateAppProxyToken, generateAppProxyUrl } from 'worker/services/aiga import { ImageType, uploadImage } from 'worker/utils/images'; import { ConversationMessage, ConversationState } from '../inferutils/common'; import { DeepCodeDebugger } from '../assistants/codeDebugger'; -import { IdGenerator } from '../utils/idGenerator'; import { DeepDebugResult } from './types'; interface WebhookPayload { @@ -852,7 +851,6 @@ export class SimpleCodeGeneratorAgent extends Agent { async executeDeepDebug( issue: string, focusPaths?: string[], - toolRenderer?: RenderToolCall ): Promise { const debugPromise = (async () => { @@ -875,16 +873,16 @@ export class SimpleCodeGeneratorAgent extends Agent { const streamCallback = (chunk: string) => { this.broadcast(WebSocketMessageResponses.CONVERSATION_RESPONSE, { message: chunk, - conversationId: `deep-debug-${IdGenerator.generateConversationId()}`, + conversationId: dbg.getConversationId(), isStreaming: true, }); }; - + const toolCallRenderer = buildToolCallRenderer(streamCallback, dbg.getConversationId()); const out = await dbg.run( { issue, previousTranscript }, { filesIndex, agent: this.codingAgent, runtimeErrors }, streamCallback, - toolRenderer, + toolCallRenderer, ); // Save transcript for next session diff --git a/worker/agents/operations/UserConversationProcessor.ts b/worker/agents/operations/UserConversationProcessor.ts index c39ac7b6..4a2a91d6 100644 --- a/worker/agents/operations/UserConversationProcessor.ts +++ b/worker/agents/operations/UserConversationProcessor.ts @@ -45,7 +45,7 @@ type ConversationResponseCallback = ( tool?: ToolCallStatusArgs ) => void; -function buildToolCallRenderer(callback: ConversationResponseCallback, conversationId: string): RenderToolCall { +export function buildToolCallRenderer(callback: ConversationResponseCallback, conversationId: string): RenderToolCall { return (args: ToolCallStatusArgs) => { callback('', conversationId, false, args); } @@ -349,7 +349,6 @@ export class UserConversationProcessor extends AgentOperation ({ ...td, onStart: (args: Record) => toolCallRenderer({ name: td.function.name, status: 'start', args }), diff --git a/worker/agents/services/implementations/CodingAgent.ts b/worker/agents/services/implementations/CodingAgent.ts index a606c70d..6f46d6e7 100644 --- a/worker/agents/services/implementations/CodingAgent.ts +++ b/worker/agents/services/implementations/CodingAgent.ts @@ -3,7 +3,6 @@ import { Blueprint } from "worker/agents/schemas"; import { ExecuteCommandsResponse, StaticAnalysisResponse, RuntimeError } from "worker/services/sandbox/sandboxTypes"; import { ICodingAgent } from "../interfaces/ICodingAgent"; import { OperationOptions } from "worker/agents/operations/common"; -import { RenderToolCall } from "worker/agents/operations/UserConversationProcessor"; import { DeepDebugResult } from "worker/agents/core/types"; /* @@ -98,8 +97,7 @@ export class CodingAgentInterface { executeDeepDebug( issue: string, focusPaths?: string[], - toolRenderer?: RenderToolCall ): Promise { - return this.agentStub.executeDeepDebug(issue, focusPaths, toolRenderer); + return this.agentStub.executeDeepDebug(issue, focusPaths); } } diff --git a/worker/agents/services/interfaces/ICodingAgent.ts b/worker/agents/services/interfaces/ICodingAgent.ts index 385eb56a..52e819ee 100644 --- a/worker/agents/services/interfaces/ICodingAgent.ts +++ b/worker/agents/services/interfaces/ICodingAgent.ts @@ -3,7 +3,6 @@ import { BaseSandboxService } from "worker/services/sandbox/BaseSandboxService"; import { ExecuteCommandsResponse, PreviewType, StaticAnalysisResponse, RuntimeError } from "worker/services/sandbox/sandboxTypes"; import { ProcessedImageAttachment } from "worker/types/image-attachment"; import { OperationOptions } from "worker/agents/operations/common"; -import { RenderToolCall } from "worker/agents/operations/UserConversationProcessor"; import { DeepDebugResult } from "worker/agents/core/types"; export abstract class ICodingAgent { @@ -46,6 +45,5 @@ export abstract class ICodingAgent { abstract executeDeepDebug( issue: string, focusPaths?: string[], - toolRenderer?: RenderToolCall ): Promise; } diff --git a/worker/agents/tools/customTools.ts b/worker/agents/tools/customTools.ts index 9993dbbd..059bb785 100644 --- a/worker/agents/tools/customTools.ts +++ b/worker/agents/tools/customTools.ts @@ -37,7 +37,6 @@ export async function executeToolWithDefinition( export function buildTools( agent: CodingAgentInterface, logger: StructuredLogger, - toolRenderer?: RenderToolCall, ): ToolDefinition[] { return [ toolWebSearchDefinition, @@ -50,7 +49,7 @@ export function buildTools( createRenameProjectTool(agent, logger), createAlterBlueprintTool(agent, logger), // Deep autonomous debugging assistant tool - createDeepDebuggerTool(agent, logger, toolRenderer), + createDeepDebuggerTool(agent, logger), ]; } diff --git a/worker/agents/tools/toolkit/deep-debugger.ts b/worker/agents/tools/toolkit/deep-debugger.ts index 9f3e0c67..7a307599 100644 --- a/worker/agents/tools/toolkit/deep-debugger.ts +++ b/worker/agents/tools/toolkit/deep-debugger.ts @@ -1,12 +1,10 @@ import { ToolDefinition } from '../types'; import { StructuredLogger } from '../../../logger'; import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; -import { RenderToolCall } from '../../operations/UserConversationProcessor'; export function createDeepDebuggerTool( agent: CodingAgentInterface, logger: StructuredLogger, - toolRenderer?: RenderToolCall, ): ToolDefinition< { issue: string; focus_paths?: string[] }, { transcript: string } | { error: string } @@ -44,7 +42,7 @@ export function createDeepDebuggerTool( } // Execute debug session - agent handles all logic internally - const result = await agent.executeDeepDebug(issue, focus_paths, toolRenderer); + const result = await agent.executeDeepDebug(issue, focus_paths); // Convert discriminated union to tool response format if (result.success) { From 185f189efc9bd5518efd83f0c17bd48848e42445 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 19:06:40 -0400 Subject: [PATCH 023/150] refactor: pass tool renderer and stream callback directly to deep debug execution --- worker/agents/core/simpleGeneratorAgent.ts | 46 +++++++++---------- .../operations/UserConversationProcessor.ts | 19 +++++++- .../services/implementations/CodingAgent.ts | 9 ++-- .../services/interfaces/ICodingAgent.ts | 5 +- worker/agents/tools/customTools.ts | 4 +- 5 files changed, 53 insertions(+), 30 deletions(-) diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index 8e22fa00..c45d912b 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -14,7 +14,7 @@ import { MAX_DEPLOYMENT_RETRIES, PREVIEW_EXPIRED_ERROR, WebSocketMessageResponse import { broadcastToConnections, handleWebSocketClose, handleWebSocketMessage } from './websocket'; import { createObjectLogger, StructuredLogger } from '../../logger'; import { ProjectSetupAssistant } from '../assistants/projectsetup'; -import { UserConversationProcessor, buildToolCallRenderer } from '../operations/UserConversationProcessor'; +import { UserConversationProcessor, RenderToolCall } from '../operations/UserConversationProcessor'; import { FileManager } from '../services/implementations/FileManager'; import { StateManager } from '../services/implementations/StateManager'; // import { WebSocketBroadcaster } from '../services/implementations/WebSocketBroadcaster'; @@ -850,6 +850,8 @@ export class SimpleCodeGeneratorAgent extends Agent { async executeDeepDebug( issue: string, + toolRenderer: RenderToolCall, + streamCb: (chunk: string) => void, focusPaths?: string[], ): Promise { @@ -869,20 +871,12 @@ export class SimpleCodeGeneratorAgent extends Agent { operationOptions.env, operationOptions.inferenceContext, ); - // Create streaming callback using broadcast mechanism - const streamCallback = (chunk: string) => { - this.broadcast(WebSocketMessageResponses.CONVERSATION_RESPONSE, { - message: chunk, - conversationId: dbg.getConversationId(), - isStreaming: true, - }); - }; - const toolCallRenderer = buildToolCallRenderer(streamCallback, dbg.getConversationId()); + const out = await dbg.run( { issue, previousTranscript }, { filesIndex, agent: this.codingAgent, runtimeErrors }, - streamCallback, - toolCallRenderer, + streamCb, + toolRenderer, ); // Save transcript for next session @@ -1466,19 +1460,11 @@ export class SimpleCodeGeneratorAgent extends Agent { const resp = await this.getSandboxServiceClient().getInstanceErrors(this.state.sandboxInstanceId); if (!resp || !resp.success) { this.logger().error(`Failed to fetch runtime errors: ${resp?.error || 'Unknown error'}, Will initiate redeploy`); - // Initiate redeploy this.deployToSandbox(); return []; } const errors = resp?.errors || []; - - if (errors.filter(error => error.message.includes('Unterminated string in JSON at position')).length > 0) { - this.logger().error('Unterminated string in JSON at position, will initiate redeploy'); - // Initiate redeploy - this.deployToSandbox(); - return []; - } if (errors.length > 0) { this.logger().info(`Found ${errors.length} runtime errors: ${errors.map(e => e.message).join(', ')}`); @@ -1839,7 +1825,7 @@ export class SimpleCodeGeneratorAgent extends Agent { this.logger().info("Waiting for preview completed"); } - async deployToSandbox(files: FileOutputType[] = [], redeploy: boolean = false, commitMessage?: string): Promise { + async deployToSandbox(files: FileOutputType[] = [], redeploy: boolean = false, commitMessage?: string, clearLogs: boolean = false): Promise { // If there's already a deployment in progress, wait for it to complete if (this.currentDeploymentPromise) { this.logger().info('Deployment already in progress, waiting for completion'); @@ -1859,7 +1845,7 @@ export class SimpleCodeGeneratorAgent extends Agent { this.logger().info("Deploying to sandbox", { files, redeploy, commitMessage, sessionId: this.state.sessionId }); // Start the actual deployment and track it - this.currentDeploymentPromise = this.executeDeployment(files, redeploy, commitMessage); + this.currentDeploymentPromise = this.executeDeployment(files, redeploy, commitMessage, clearLogs); // Create timeout that resets session if deployment hangs let timeoutId: ReturnType | null = null; @@ -2025,7 +2011,7 @@ export class SimpleCodeGeneratorAgent extends Agent { }; } - private async executeDeployment(files: FileOutputType[] = [], redeploy: boolean = false, commitMessage?: string, retries: number = MAX_DEPLOYMENT_RETRIES): Promise { + private async executeDeployment(files: FileOutputType[] = [], redeploy: boolean = false, commitMessage?: string, clearLogs: boolean = false, retries: number = MAX_DEPLOYMENT_RETRIES): Promise { try { this.broadcast(WebSocketMessageResponses.DEPLOYMENT_STARTED, { message: "Deploying code to sandbox service", @@ -2061,6 +2047,18 @@ export class SimpleCodeGeneratorAgent extends Agent { throw new Error(`File writing failed. Error: ${writeResponse?.error}`); } } + if (clearLogs) { + try { + this.logger().info('Clearing logs and runtime errors for instance', { instanceId: sandboxInstanceId }); + await Promise.all([ + this.getSandboxServiceClient().getLogs(sandboxInstanceId, true), + this.getSandboxServiceClient().clearInstanceErrors(sandboxInstanceId) + ]); + } catch (error) { + this.logger().error('Failed to clear logs and runtime errors', error); + } + } + const preview = { runId: sandboxInstanceId, @@ -2092,7 +2090,7 @@ export class SimpleCodeGeneratorAgent extends Agent { }); // Wait for exponential backoff await new Promise(resolve => setTimeout(resolve, Math.pow(2, MAX_DEPLOYMENT_RETRIES - retries) * 1000)); - return this.executeDeployment(files, redeploy, commitMessage, retries - 1); + return this.executeDeployment(files, redeploy, commitMessage, clearLogs, retries - 1); } this.broadcast(WebSocketMessageResponses.DEPLOYMENT_FAILED, { error: `Error deploying to sandbox service: ${errorMsg}. Please report an issue if this persists`, diff --git a/worker/agents/operations/UserConversationProcessor.ts b/worker/agents/operations/UserConversationProcessor.ts index 4a2a91d6..18d39aa2 100644 --- a/worker/agents/operations/UserConversationProcessor.ts +++ b/worker/agents/operations/UserConversationProcessor.ts @@ -135,7 +135,22 @@ Use the deep_debug tool to investigate and fix bugs immediately. This synchronou - Apply surgical fixes - Stream progress directly to the user -When you call deep_debug, it runs to completion and returns a transcript. The user will see all the debugging steps in real-time. After it returns, you can acknowledge completion: "The debugging session is complete. The issue should be resolved." +When you call deep_debug, it runs to completion and returns a transcript. The user will see all the debugging steps in real-time. + +**CRITICAL - After deep_debug completes:** +- **If transcript contains "TASK_COMPLETE" AND runtime errors show "N/A":** + - ✅ Acknowledge success: "The debugging session successfully resolved the [specific issue]." + - ✅ If user asks for another session: Frame it as verification, not fixing: "I'll verify everything is working correctly and check for any other issues." + - ❌ DON'T say: "fix remaining issues" or "problems that weren't fully resolved" - this misleads the user + - ❌ DON'T reference past failed attempts when the issue is now fixed + +- **If transcript shows incomplete work or errors persist:** + - Acknowledge what was attempted and what remains + - Be specific about next steps + +**Examples:** +❌ BAD (after successful fix): "I'll debug any remaining issues that might not have been fully resolved" +✅ GOOD (after successful fix): "The previous session fixed the issue. I'll verify everything is stable and check for any new issues." **CRITICAL - If deep_debug returns GENERATION_IN_PROGRESS error:** 1. Tell user: "Code generation is in progress. Let me wait for it to complete..." @@ -349,6 +364,8 @@ export class UserConversationProcessor extends AgentOperation inputs.conversationResponseCallback(chunk, aiConversationId, true) ).map(td => ({ ...td, onStart: (args: Record) => toolCallRenderer({ name: td.function.name, status: 'start', args }), diff --git a/worker/agents/services/implementations/CodingAgent.ts b/worker/agents/services/implementations/CodingAgent.ts index 6f46d6e7..4bd5b6ad 100644 --- a/worker/agents/services/implementations/CodingAgent.ts +++ b/worker/agents/services/implementations/CodingAgent.ts @@ -4,6 +4,7 @@ import { ExecuteCommandsResponse, StaticAnalysisResponse, RuntimeError } from "w import { ICodingAgent } from "../interfaces/ICodingAgent"; import { OperationOptions } from "worker/agents/operations/common"; import { DeepDebugResult } from "worker/agents/core/types"; +import { RenderToolCall } from "worker/agents/operations/UserConversationProcessor"; /* * CodingAgentInterface - stub for passing to tool calls @@ -22,8 +23,8 @@ export class CodingAgentInterface { return this.agentStub.fetchRuntimeErrors(clear); } - async deployPreview(): Promise { - const response = await this.agentStub.deployToSandbox([], false); + async deployPreview(clearLogs: boolean = true): Promise { + const response = await this.agentStub.deployToSandbox([], false, undefined, clearLogs); if (response && response.previewURL) { return `Deployment successful: ${response.previewURL}`; } else { @@ -96,8 +97,10 @@ export class CodingAgentInterface { executeDeepDebug( issue: string, + toolRenderer: RenderToolCall, + streamCb: (chunk: string) => void, focusPaths?: string[], ): Promise { - return this.agentStub.executeDeepDebug(issue, focusPaths); + return this.agentStub.executeDeepDebug(issue, toolRenderer, streamCb, focusPaths); } } diff --git a/worker/agents/services/interfaces/ICodingAgent.ts b/worker/agents/services/interfaces/ICodingAgent.ts index 52e819ee..61ac752b 100644 --- a/worker/agents/services/interfaces/ICodingAgent.ts +++ b/worker/agents/services/interfaces/ICodingAgent.ts @@ -4,11 +4,12 @@ import { ExecuteCommandsResponse, PreviewType, StaticAnalysisResponse, RuntimeEr import { ProcessedImageAttachment } from "worker/types/image-attachment"; import { OperationOptions } from "worker/agents/operations/common"; import { DeepDebugResult } from "worker/agents/core/types"; +import { RenderToolCall } from "worker/agents/operations/UserConversationProcessor"; export abstract class ICodingAgent { abstract getSandboxServiceClient(): BaseSandboxService; - abstract deployToSandbox(files: FileOutputType[], redeploy: boolean, commitMessage?: string): Promise; + abstract deployToSandbox(files: FileOutputType[], redeploy: boolean, commitMessage?: string, clearLogs?: boolean): Promise; abstract deployToCloudflare(): Promise<{ deploymentUrl?: string; workersUrl?: string } | null>; @@ -44,6 +45,8 @@ export abstract class ICodingAgent { abstract executeDeepDebug( issue: string, + toolRenderer: RenderToolCall, + streamCb: (chunk: string) => void, focusPaths?: string[], ): Promise; } diff --git a/worker/agents/tools/customTools.ts b/worker/agents/tools/customTools.ts index 059bb785..cca8a3e3 100644 --- a/worker/agents/tools/customTools.ts +++ b/worker/agents/tools/customTools.ts @@ -37,6 +37,8 @@ export async function executeToolWithDefinition( export function buildTools( agent: CodingAgentInterface, logger: StructuredLogger, + toolRenderer: RenderToolCall, + streamCb: (chunk: string) => void, ): ToolDefinition[] { return [ toolWebSearchDefinition, @@ -49,7 +51,7 @@ export function buildTools( createRenameProjectTool(agent, logger), createAlterBlueprintTool(agent, logger), // Deep autonomous debugging assistant tool - createDeepDebuggerTool(agent, logger), + createDeepDebuggerTool(agent, logger, toolRenderer, streamCb), ]; } From 0c3a841b72497bd97169de907a806d8527ff0abd Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 19:07:40 -0400 Subject: [PATCH 024/150] fix: prompt react fixes --- worker/agents/prompts.ts | 60 ++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/worker/agents/prompts.ts b/worker/agents/prompts.ts index 79c16f93..4bee946c 100644 --- a/worker/agents/prompts.ts +++ b/worker/agents/prompts.ts @@ -110,7 +110,7 @@ and provide a preview url for the application. const errorText = e.message; // Remove any trace lines with no 'tsx' or 'ts' extension in them const cleanedText = errorText.split('\n') - .map(line => line.includes('/deps/') && !(line.includes('.tsx') || line.includes('.ts')) ? '...' : line) + .map(line => line.includes('/deps/') && !(line.includes('.tsx') || line.includes('.ts')) ? '' : line).filter(line => line.trim() !== '') .join('\n'); // Truncate to 1000 characters to prevent context overflow return `${cleanedText.slice(0, 1000)}`; @@ -582,9 +582,15 @@ const value = useMemo(() => ({ user, setUser }), [user]); ❌ FORBIDDEN PATTERNS (ALL CAUSE INFINITE LOOPS): \`\`\`tsx -// Pattern 1: Object literal selector without useShallow +// Pattern 1: Object literal selector (with or without useShallow) const { a, b, c } = useStore(s => ({ a: s.a, b: s.b, c: s.c })); // ❌ CRASH +// Pattern 1b: useShallow DOES NOT FIX object literal selectors! +import { useShallow } from 'zustand/react/shallow'; +const { a, b, c } = useStore(useShallow(s => ({ a: s.a, b: s.b, c: s.c }))); // ❌ STILL CRASHES! +// Why? You're creating a NEW object ({ a, b, c }) every render in the selector +// useShallow can't help - the object reference is new every time + // Pattern 2: No selector (returns whole state object) const { a, b, c } = useStore(); // ❌ CRASH const state = useStore(); // ❌ CRASH @@ -595,19 +601,25 @@ const filtered = useStore(s => s.items.filter(...)); // ❌ INFINITE LOOP const mapped = useStore(s => s.data.map(...)); // ❌ INFINITE LOOP \`\`\` +⚠️ CRITICAL MISCONCEPTION - READ THIS: +Many developers see "object literal selector without useShallow" and think "with useShallow" fixes it. +NO! useShallow is ONLY for objects that ALREADY EXIST in your store, not for creating new objects. + ✅ CORRECT PATTERNS (CHOOSE ONE): \`\`\`tsx -// Option 1: Separate primitive selectors (RECOMMENDED - foolproof) +// Option 1: Separate primitive selectors (RECOMMENDED - MOST EFFICIENT) const a = useStore(s => s.a); const b = useStore(s => s.b); const c = useStore(s => s.c); +// ⚡ EFFICIENCY: Each selector ONLY triggers re-render when ITS value changes +// This is NOT inefficient! It's the BEST pattern for Zustand. -// Option 2: shallow equality for STABLE objects (advanced; only if the object -// reference comes directly from the store and does not get re-created). Do NOT -// allocate objects in the selector. -import { shallow } from 'zustand/shallow'; -const viewport = useStore(s => s.viewport, shallow); // ✅ stable object from store -// ❌ BAD: const v = useStore(s => ({ ...s.viewport })); // allocates new object +// Option 2: useShallow for objects ALREADY IN the store (RARE - advanced) +import { useShallow } from 'zustand/react/shallow'; +const viewport = useStore(useShallow(s => s.viewport)); +// ✅ ONLY when 'viewport' is an object that EXISTS in your store: +// const store = create((set) => ({ viewport: { x: 0, y: 0 }, ... })) +// ❌ NOT for creating new objects: useStore(useShallow(s => ({ x: s.x, y: s.y }))) // Option 3: Store methods → Select primitives + useMemo in component const items = useStore(s => s.items); @@ -618,6 +630,36 @@ const filtered = useMemo(() => ); \`\`\` +💡 IMPORTANT: Multiple Individual Selectors is MOST EFFICIENT (Debunking Common Myth) + +❌ WRONG BELIEF: "Multiple useStore calls = inefficient = many re-renders" +✅ TRUTH: Each selector ONLY triggers re-render when ITS specific value changes + +Example: +\`\`\`tsx +const name = useStore(s => s.user.name); // Subscribes to name only +const count = useStore(s => s.count); // Subscribes to count only + +// If count changes: +// ✓ count selector triggers ONE re-render +// ✓ name selector does NOT trigger (name didn't change) +// Result: ONE re-render total - perfectly efficient! +\`\`\` + +Contrast with object selector (even with useShallow): +\`\`\`tsx +const { name, count } = useStore(useShallow(s => ({ + name: s.user.name, + count: s.count +}))); + +// If count changes: +// ✗ Creates NEW object { name, count } every render +// ✗ useShallow sees count changed, triggers re-render +// ✗ NEW object creation itself can cause infinite loop +// Result: LESS efficient + risk of crash +\`\`\` + ⚠️ CRITICAL DIFFERENCES: \`\`\`tsx // This works fine in React Context (context-based): From 06375ce09891d311acc8b283391d2378a50f35f0 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 19:08:13 -0400 Subject: [PATCH 025/150] fix: code debugger prompt improvements --- worker/agents/assistants/codeDebugger.ts | 169 ++++++++++++++--------- 1 file changed, 106 insertions(+), 63 deletions(-) diff --git a/worker/agents/assistants/codeDebugger.ts b/worker/agents/assistants/codeDebugger.ts index ef10f5a3..30c9a0f1 100644 --- a/worker/agents/assistants/codeDebugger.ts +++ b/worker/agents/assistants/codeDebugger.ts @@ -37,31 +37,28 @@ You are working on a **Cloudflare Workers** project (optionally with Durable Obj - **Build**: Typically uses Vite or similar for bundling - **Deployment**: via wrangler to Cloudflare edge -**CRITICAL CONSTRAINTS:** -- **NEVER edit wrangler.jsonc or package.json** - these are locked/managed externally -- If you think the issue requires changing these files, report it's impossible to fix - -## How the Platform & Logs Work (IMPORTANT) -This is an AI coding platform with a sandbox environment: -- **Sandbox Preview**: Apps run in a Cloudflare Workers sandbox with a live preview URL -- **Logs are USER-DRIVEN**: Runtime logs (get_logs) only appear when the USER interacts with the app - - After you deploy changes, logs won't appear until the user actually clicks buttons, navigates pages, etc. - - If you add console.log statements and deploy, you MUST wait for user interaction to see those logs - - **DO NOT repeatedly check logs expecting new output if the user hasn't interacted with the app** - -**CRITICAL WORKFLOW for Runtime Verification:** -1. Deploy changes: deploy_preview -2. Wait for interaction: wait(20-30, "Waiting for user to interact") -3. Check logs: get_logs -4. If logs empty, user hasn't interacted - inform them and wait longer OR use static analysis - -- **Static Analysis is IMMEDIATE**: run_analysis doesn't require user interaction - use this for verification. But you need to deploy changes first to meaningfully run static analysis -- **When logging isn't working**: If you need to debug but logs aren't appearing: - - State clearly: "I've added logging. Please interact with the app (click buttons, navigate) to generate logs, then I can continue." - - OR use static analysis and code review instead of relying on runtime logs - - Don't get stuck in a loop trying to check logs when user hasn't interacted - -**Always make sure to deploy your changes before running static analysis or fetching logs** +## Platform Constraints +- Apps run in Cloudflare Workers sandbox with live preview +- **NEVER edit wrangler.jsonc or package.json** - report if these need changes +- Logs/errors are USER-DRIVEN - only appear when users interact with the app +- **Deploy before verification**: Always deploy_preview before running static analysis or checking logs + +## CRITICAL: Logs Are Cumulative (Verification Required) +**Logs accumulate and are NOT cleared** - errors from before your fixes will still appear in get_logs. + +**BEFORE fixing any issue, verify it still exists:** +1. **Check initial runtime errors** provided in your context (if any) - these may be stale +2. **Cross-reference multiple sources**: Compare get_logs, get_runtime_errors, and actual code +3. **Read the actual code**: Confirm the bug is present before attempting to fix +4. **Check timestamps**: Determine if errors occurred before or after your fixes +5. **Don't fix the same issue twice** - if code already has the fix, move on + +**Verification Workflow:** +1. deploy_preview (if you made changes) +2. run_analysis (fast, immediate verification) +3. If needed: wait(20-30, "Waiting for user interaction") → get_runtime_errors +4. If still unclear: get_logs (sparingly, with reset=true if starting fresh) +5. read_files to confirm bug exists in code before fixing ## Your Approach You are methodical and evidence-based. You choose your own path to solve issues, but always verify fixes work before claiming success. @@ -74,18 +71,22 @@ You are methodical and evidence-based. You choose your own path to solve issues, - Your reasoning_effort is set to HIGH - leverage this for complex analysis **Required Workflow:** -1. Run initial diagnostic tools (run_analysis, get_logs, or read_files) -2. **Internally create a debugging plan** - analyze in your reasoning, don't output verbose plans -3. **Execute decisively** - Make tool calls with minimal commentary -4. **Verify fixes** - Call run_analysis or get_logs after fixes -5. **Provide concise final report** - Brief summary of what was done +1. **Diagnose**: Start with run_analysis and get_runtime_errors. Only use get_logs if these lack detail. +2. **Plan internally**: Analyze in your reasoning, don't output verbose plans +3. **Execute decisively**: Make tool calls with minimal commentary +4. **Verify fixes**: Prefer run_analysis (fast, reliable). Use get_runtime_errors or get_logs only if needed. +5. **Report concisely**: Brief summary of what was done ## Available Tools -Use these tools flexibly based on what you need: - -- **get_logs**: Fetch runtime errors and console output from Workers runtime -- **get_runtime_errors**: Fetch latest runtime errors from sandbox storage (user-interaction driven, may be stale) -- **run_analysis**: Run lint + typecheck (optionally scope to specific files) +**Diagnostic Priority (use in this order):** +1. **run_analysis** - Fast, static, no user interaction needed (START HERE) +2. **get_runtime_errors** - Recent runtime errors, more reliable than logs +3. **get_logs** - Use SPARINGLY, only when above tools lack detail. Verbose and cumulative. + +**Tools:** +- **run_analysis**: Lint + typecheck. Fast, always works. **Use this first for verification.** +- **get_runtime_errors**: Recent runtime errors (user-driven). More reliable than logs. +- **get_logs**: Cumulative logs (verbose, user-driven). **Use sparingly** - only when runtime errors lack detail. Set reset=true to clear stale logs. - **read_files**: Read file contents by RELATIVE paths (batch multiple in one call for efficiency) - **exec_commands**: Execute shell commands from project root (no cd needed) - **regenerate_file**: Autonomous surgical code fixer - see detailed guide below @@ -145,11 +146,31 @@ issues: [ } \`\`\` +**PARALLEL EXECUTION (IMPORTANT):** +- **You can call regenerate_file on MULTIPLE files simultaneously** +- If you need to fix issues in 3+ different files, call all regenerate_file operations in parallel +- This is much faster than sequential calls +- Only requirement: files must be independent (not fixing the same file twice) + +**Example - Parallel calls:** +\`\`\`typescript +// ✅ GOOD - Fix 3 files at once +regenerate_file({ path: "src/components/App.tsx", issues: [...] }) +regenerate_file({ path: "src/stores/store.ts", issues: [...] }) +regenerate_file({ path: "src/utils/helpers.ts", issues: [...] }) +// All execute simultaneously + +// ❌ BAD - Don't call same file twice in parallel +regenerate_file({ path: "src/App.tsx", issues: ["Fix error A"] }) +regenerate_file({ path: "src/App.tsx", issues: ["Fix error B"] }) +// This will conflict - combine into one call instead +\`\`\` + **CRITICAL: After calling regenerate_file:** 1. **READ THE DIFF** - Always examine what changed 2. **VERIFY THE FIX** - Check if the diff addresses the reported issues 3. **DON'T REGENERATE AGAIN** if the diff shows the fix was already applied -4. **RUN run_analysis** after fixes to verify no new errors were introduced +4. **RUN run_analysis, get_runtime_errors or get_logs** after fixes to verify no new errors were introduced. You might have to wait for some time, and prompt the user appropriately for the logs to appear. **When to use regenerate_file:** - ✅ TypeScript/JavaScript errors that need code changes @@ -170,25 +191,25 @@ issues: [ - All paths are RELATIVE to project root (sandbox pwd = project directory) - Commands execute from project root automatically - Never use 'cd' commands -- Prefer batching parallel tool calls when possible +- **Prefer batching parallel tool calls when possible** - especially regenerate_file on different files, read_files for multiple files ## Core Principles **Pay Attention to Tool Results** -- **CRITICAL**: Always read and understand what tools return, especially: - - regenerate_file returns 'diff' showing exactly what changed - review it before claiming you misread something - - If the diff shows the code already has what you wanted, DON'T regenerate again - - run_analysis returns specific errors - read them carefully - - get_logs shows actual runtime behavior - analyze what's happening -- **Before calling regenerate_file**: Read the current file content first to confirm the issue exists -- **After calling regenerate_file**: Check the returned diff to verify the change was correct +- **regenerate_file** returns 'diff' - review it; if code already correct, DON'T regenerate again +- **run_analysis** returns specific errors - read them carefully +- **get_logs** shows cumulative logs - **CRITICAL: May contain old errors from before your fixes** + - Always check timestamps vs. your deploy times + - Cross-reference with get_runtime_errors and actual code + - Don't fix issues that were already resolved +- **Before regenerate_file**: Read current code to confirm bug exists +- **After regenerate_file**: Check diff to verify correctness **Verification is Mandatory** -- First thoroughly and deeply debug and verify if the problem actually exists and your theory is correct -- After applying any fix, ALWAYS verify it worked via get_logs or run_analysis -- Never claim success without proof -- If errors persist, iterate with a different approach -- get_logs would return the last X seconds of logs, but these might contain stale logs as well. Always cross reference timestamps of logs with timestamps of project updates or past messages to verify if the logs are relevant +- **BEFORE fixing**: Verify the problem exists in current code (initial runtime errors may be stale) +- **AFTER fixing**: Verify it worked via run_analysis, get_runtime_errors, or code review +- **Cross-reference sources**: Logs + runtime errors + code must all agree before fixing +- Never claim success without proof; iterate if errors persist **Minimize Changes** - Apply surgical, minimal fixes - change only what's necessary and when you are absolutely sure of it @@ -219,6 +240,29 @@ issues: [ - **Type safety**: maintain strict TypeScript compliance - **Configuration files**: Never try to edit wrangler.jsonc or package.json +**⚠️ CRITICAL: Do NOT "Optimize" Zustand Selectors** +If you see this pattern - **LEAVE IT ALONE** (it's already optimal): +\`\`\`tsx +const x = useStore(s => s.x); +const y = useStore(s => s.y); +const z = useStore(s => s.z); +\`\`\` + +❌ DO NOT consolidate multiple selectors into object selector +❌ DO NOT assume "multiple hooks = inefficient" +✅ Multiple individual selectors IS the recommended pattern +✅ Each selector only triggers re-render when its specific value changes + +❌ NEVER "fix" by adding useShallow to object literals: +\`\`\`tsx +// ❌ WRONG - This introduces infinite loop: +const { x, y } = useStore(useShallow(s => ({ x: s.x, y: s.y }))); + +// ✅ CORRECT - Keep it as individual selectors: +const x = useStore(s => s.x); +const y = useStore(s => s.y); +\`\`\` + ## Success Criteria You're done when: 1. ✅ Errors cleared AND verified via logs/analysis @@ -290,15 +334,21 @@ ${templateInfo} **IMPORTANT:** These are the available components, utilities, and APIs in the project. Always verify imports against this list.` : ''} -${runtimeErrors ? `## Latest Runtime Errors (May be stale) -These runtime errors were captured from the sandbox. Note that they may be a few seconds old and are driven by user interactions with the app. +${runtimeErrors ? `## Initial Runtime Errors (MAY BE STALE - VERIFY BEFORE FIXING) +These runtime errors were captured earlier. **CRITICAL: Verify each error still exists before attempting to fix.** + +**Before fixing any error below:** +1. Read the actual code to confirm the bug is present +2. Cross-reference with fresh get_runtime_errors and get_logs +3. Check if previous fixes already resolved it +4. Don't fix the same issue twice -**CRITICAL:** Runtime errors only appear when users interact with the app (clicking buttons, navigating, etc.). If you need fresh errors: -1. Deploy your changes with deploy_preview -2. Use wait(20-30) to allow time for user interaction -3. Then call get_runtime_errors to fetch latest errors +${runtimeErrors} -${runtimeErrors}` : ''} +**To get fresh errors after your fixes:** +1. deploy_preview +2. wait(20-30, "Waiting for user interaction") +3. get_runtime_errors + get_logs (cross-reference both)` : ''} ## Your Mission Diagnose and fix all user issues. @@ -357,8 +407,6 @@ export class DeepCodeDebugger extends Assistant { repetitionWarnings: 0, }; - private conversationId: string; - constructor( env: Env, inferenceContext: InferenceContext, @@ -366,11 +414,6 @@ export class DeepCodeDebugger extends Assistant { ) { super(env, inferenceContext); this.modelConfigOverride = modelConfigOverride; - this.conversationId = `deep-debug-${IdGenerator.generateConversationId()}`; - } - - getConversationId(): string { - return this.conversationId; } private detectRepetition(toolName: string, args: Record): boolean { From 1bdb1e7e97c6a18ae3c3a2c69b8c14e6fdd65a19 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 19:08:29 -0400 Subject: [PATCH 026/150] feat: add streaming support to deep debugger and enhance logs tool with truncation --- worker/agents/tools/toolkit/deep-debugger.ts | 5 +- worker/agents/tools/toolkit/deploy-preview.ts | 2 +- worker/agents/tools/toolkit/get-logs.ts | 57 ++++++++++++++----- 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/worker/agents/tools/toolkit/deep-debugger.ts b/worker/agents/tools/toolkit/deep-debugger.ts index 7a307599..037ea131 100644 --- a/worker/agents/tools/toolkit/deep-debugger.ts +++ b/worker/agents/tools/toolkit/deep-debugger.ts @@ -1,10 +1,13 @@ import { ToolDefinition } from '../types'; import { StructuredLogger } from '../../../logger'; import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; +import { RenderToolCall } from 'worker/agents/operations/UserConversationProcessor'; export function createDeepDebuggerTool( agent: CodingAgentInterface, logger: StructuredLogger, + toolRenderer: RenderToolCall, + streamCb: (chunk: string) => void, ): ToolDefinition< { issue: string; focus_paths?: string[] }, { transcript: string } | { error: string } @@ -42,7 +45,7 @@ export function createDeepDebuggerTool( } // Execute debug session - agent handles all logic internally - const result = await agent.executeDeepDebug(issue, focus_paths); + const result = await agent.executeDeepDebug(issue, toolRenderer, streamCb, focus_paths); // Convert discriminated union to tool response format if (result.success) { diff --git a/worker/agents/tools/toolkit/deploy-preview.ts b/worker/agents/tools/toolkit/deploy-preview.ts index c51f5ffc..cf9e75ef 100644 --- a/worker/agents/tools/toolkit/deploy-preview.ts +++ b/worker/agents/tools/toolkit/deploy-preview.ts @@ -15,7 +15,7 @@ export function createDeployPreviewTool( function: { name: 'deploy_preview', description: - 'Deploys the current application to a preview environment. After deployment, the app is live at the preview URL, but runtime logs (get_logs) will only appear when the user interacts with the app - not automatically after deployment. CRITICAL: After deploying, use wait(20-30) to allow time for user interaction before checking logs.', + 'Uploads and syncs the current application to the preview environment. After deployment, the app is live at the preview URL, but runtime logs (get_logs) will only appear when the user interacts with the app - not automatically after deployment. CRITICAL: After deploying, use wait(20-30) to allow time for user interaction before checking logs.', parameters: { type: 'object', properties: {}, diff --git a/worker/agents/tools/toolkit/get-logs.ts b/worker/agents/tools/toolkit/get-logs.ts index 74b6e571..a5401058 100644 --- a/worker/agents/tools/toolkit/get-logs.ts +++ b/worker/agents/tools/toolkit/get-logs.ts @@ -5,6 +5,7 @@ import { CodingAgentInterface } from 'worker/agents/services/implementations/Cod type GetLogsArgs = { reset?: boolean; durationSeconds?: number; + maxLines?: number; }; type GetLogsResult = { logs: string } | ErrorResult; @@ -18,26 +19,37 @@ export function createGetLogsTool( function: { name: 'get_logs', description: - `Get the current application/server logs from the sandbox environment. Useful for debugging runtime issues, checking console output, or investigating errors. Clears logs every time when called. App\'s browser console warn/error messages are also piped here. -All unread logs would be streamed, so you need to match timestamps on your own to understand things. + `Get cumulative application/server logs from the sandbox environment. -IMPORTANT: Logs are USER-DRIVEN - they only appear when the user interacts with the app (clicks buttons, navigates pages, etc.). +**USE SPARINGLY:** Only call when get_runtime_errors and run_analysis don't provide enough information. Logs are verbose and cumulative - prefer other diagnostic tools first. -CRITICAL PATTERN: -- After deploy_preview: DO NOT immediately call get_logs -- Instead, if logs are needed: Call wait(20-30 seconds, "Waiting for user to interact with app") -- Then: Call get_logs to check results -- If logs are empty, it means user hasn't interacted yet - ask them to interact and wait longer +**CRITICAL:** Logs are cumulative (NOT cleared unless reset=true). Errors from before your fixes may still appear: +1. Cross-reference with get_runtime_errors (more recent) +2. Re-read actual code to confirm bug is present +3. Check timestamps vs. your deploy times -If logs are not needed, don't call this. +**WHEN TO USE:** +- ✅ Need to see console output or detailed execution flow +- ✅ Runtime errors lack detail and static analysis passes +- ❌ DON'T use as first diagnostic - try get_runtime_errors and run_analysis first -If you can't wait for user interaction, use static analysis (run_analysis) instead.`, +**DEFAULTS:** 30s window, 100 lines, no reset. Logs are USER-DRIVEN (require user interaction). + +**RESET:** Set reset=true to clear accumulated logs before fetching. Use when starting fresh debugging or after major fixes.`, parameters: { type: 'object', properties: { + reset: { + type: 'boolean', + description: 'Clear accumulated logs before fetching. Default: false. Set to true when starting fresh debugging or after major fixes to avoid stale errors.', + }, durationSeconds: { type: 'number', - description: 'Optional: Filter logs to only show entries from the last N seconds. If not specified, returns all logs till date. Useful to reduce noise when you only need recent activity.', + description: 'Time window in seconds. Default: 30 seconds (recent activity). Set to higher value if you need older logs.', + }, + maxLines: { + type: 'number', + description: 'Maximum lines to return. Default: 100. Set to -1 for no truncation (warning: heavy token usage). Increase to 200-500 for more context.', }, }, required: [], @@ -45,8 +57,27 @@ If you can't wait for user interaction, use static analysis (run_analysis) inste }, implementation: async (args?) => { try { - logger.info('Fetching application logs', { durationSeconds: args?.durationSeconds }); - const logs = await agent.getLogs(false, args?.durationSeconds); + const reset = args?.reset ?? false; // Default: don't reset + const durationSeconds = args?.durationSeconds ?? 30; // Default to last 30 seconds + const maxLines = args?.maxLines ?? 100; // Default to 100 lines + + logger.info('Fetching application logs', { reset, durationSeconds, maxLines }); + const logs = await agent.getLogs(reset, durationSeconds); + + // Truncate logs if maxLines is not -1 + if (maxLines !== -1 && logs) { + const lines = logs.split('\n'); + if (lines.length > maxLines) { + const truncatedLines = lines.slice(-maxLines); // Keep last N lines (most recent) + const truncatedLog = [ + `[TRUNCATED: Showing last ${maxLines} of ${lines.length} lines. Set maxLines higher or to -1 for full output]`, + ...truncatedLines + ].join('\n'); + logger.info('Logs truncated', { originalLines: lines.length, truncatedLines: maxLines }); + return { logs: truncatedLog }; + } + } + return { logs }; } catch (error) { return { From 796e34d3459657502599bdbfdeddc1144289c0e3 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 19:14:46 -0400 Subject: [PATCH 027/150] feat: add force refresh mechanism for preview deployments --- src/routes/chat/utils/handle-websocket-message.ts | 8 ++++++++ worker/agents/constants.ts | 1 + worker/agents/services/implementations/CodingAgent.ts | 3 +++ worker/agents/services/interfaces/ICodingAgent.ts | 3 +++ worker/api/websocketTypes.ts | 5 +++++ 5 files changed, 20 insertions(+) diff --git a/src/routes/chat/utils/handle-websocket-message.ts b/src/routes/chat/utils/handle-websocket-message.ts index f47508f3..f726ac07 100644 --- a/src/routes/chat/utils/handle-websocket-message.ts +++ b/src/routes/chat/utils/handle-websocket-message.ts @@ -583,6 +583,14 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { break; } + case 'preview_force_refresh': { + setShouldRefreshPreview(true); + setTimeout(() => { + setShouldRefreshPreview(false); + }, 100); + break; + } + case 'generation_stopped': { setIsGenerating(false); setIsGenerationPaused(true); diff --git a/worker/agents/constants.ts b/worker/agents/constants.ts index 89953973..58d0644c 100644 --- a/worker/agents/constants.ts +++ b/worker/agents/constants.ts @@ -25,6 +25,7 @@ export const WebSocketMessageResponses: Record = { DEPLOYMENT_STARTED: 'deployment_started', DEPLOYMENT_COMPLETED: 'deployment_completed', DEPLOYMENT_FAILED: 'deployment_failed', + PREVIEW_FORCE_REFRESH: 'preview_force_refresh', // Cloudflare deployment messages CLOUDFLARE_DEPLOYMENT_STARTED: 'cloudflare_deployment_started', CLOUDFLARE_DEPLOYMENT_COMPLETED: 'cloudflare_deployment_completed', diff --git a/worker/agents/services/implementations/CodingAgent.ts b/worker/agents/services/implementations/CodingAgent.ts index 4bd5b6ad..6e980d71 100644 --- a/worker/agents/services/implementations/CodingAgent.ts +++ b/worker/agents/services/implementations/CodingAgent.ts @@ -5,6 +5,7 @@ import { ICodingAgent } from "../interfaces/ICodingAgent"; import { OperationOptions } from "worker/agents/operations/common"; import { DeepDebugResult } from "worker/agents/core/types"; import { RenderToolCall } from "worker/agents/operations/UserConversationProcessor"; +import { WebSocketMessageResponses } from "worker/agents/constants"; /* * CodingAgentInterface - stub for passing to tool calls @@ -25,7 +26,9 @@ export class CodingAgentInterface { async deployPreview(clearLogs: boolean = true): Promise { const response = await this.agentStub.deployToSandbox([], false, undefined, clearLogs); + // Send a message to refresh the preview if (response && response.previewURL) { + this.agentStub.broadcast(WebSocketMessageResponses.PREVIEW_FORCE_REFRESH, {}); return `Deployment successful: ${response.previewURL}`; } else { return `Failed to deploy: ${response?.tunnelURL}`; diff --git a/worker/agents/services/interfaces/ICodingAgent.ts b/worker/agents/services/interfaces/ICodingAgent.ts index 61ac752b..d297d1bc 100644 --- a/worker/agents/services/interfaces/ICodingAgent.ts +++ b/worker/agents/services/interfaces/ICodingAgent.ts @@ -5,6 +5,7 @@ import { ProcessedImageAttachment } from "worker/types/image-attachment"; import { OperationOptions } from "worker/agents/operations/common"; import { DeepDebugResult } from "worker/agents/core/types"; import { RenderToolCall } from "worker/agents/operations/UserConversationProcessor"; +import { WebSocketMessageType, WebSocketMessageData } from "worker/api/websocketTypes"; export abstract class ICodingAgent { abstract getSandboxServiceClient(): BaseSandboxService; @@ -43,6 +44,8 @@ export abstract class ICodingAgent { abstract waitForDeepDebug(): Promise; + abstract broadcast(message: T, data?: WebSocketMessageData): void; + abstract executeDeepDebug( issue: string, toolRenderer: RenderToolCall, diff --git a/worker/api/websocketTypes.ts b/worker/api/websocketTypes.ts index 72df30d5..f02f07b3 100644 --- a/worker/api/websocketTypes.ts +++ b/worker/api/websocketTypes.ts @@ -86,6 +86,10 @@ type DeploymentCompletedMessage = { message: string; }; +type PreviewForceRefreshMessage = { + type: 'preview_force_refresh'; +}; + type CommandExecutingMessage = { type: 'command_executing'; message: string; @@ -416,6 +420,7 @@ export type WebSocketMessage = | DeploymentStartedMessage | DeploymentCompletedMessage | DeploymentFailedMessage + | PreviewForceRefreshMessage | CodeReviewingMessage | CodeReviewedMessage | CommandExecutingMessage From 8f172ba879e484dc5c4a2bef1311ef3106c28d61 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 19:24:07 -0400 Subject: [PATCH 028/150] refactor: remove redundant tool call filtering --- worker/agents/inferutils/core.ts | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/worker/agents/inferutils/core.ts b/worker/agents/inferutils/core.ts index c20c29ce..b849f0d2 100644 --- a/worker/agents/inferutils/core.ts +++ b/worker/agents/inferutils/core.ts @@ -700,28 +700,8 @@ export async function infer({ depth: newDepth }; - // Filter out tools with empty/meaningless results to avoid duplicate responses - const executedCallsWithResults = executedToolCalls.filter(result => { - if (!result.result) return false; - - // Check if result is effectively empty - const stringResult = typeof result.result === 'string' - ? result.result - : JSON.stringify(result.result); - - // Skip if result is null, empty string, "done", or just whitespace - if (!stringResult || - stringResult === 'null' || - stringResult === '""' || - stringResult === 'done' || - stringResult.trim() === '') { - console.log(`[TOOL_CALL_SKIP] Skipping redundant LLM call for tool '${result.name}' with empty result: ${stringResult}`); - return false; - } - - return true; - }); - console.log(`Tool calling depth: ${newDepth}/${MAX_TOOL_CALLING_DEPTH}, calls with meaningful results: ${executedCallsWithResults.length}/${executedToolCalls.length}`); + const executedCallsWithResults = executedToolCalls.filter(result => result.result); + console.log(`Tool calling depth: ${newDepth}/${MAX_TOOL_CALLING_DEPTH}`); if (executedCallsWithResults.length) { if (schema && schemaName) { From f3e545e742b7b4cedb4c74b3af9501327ee8a8b3 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 19:32:33 -0400 Subject: [PATCH 029/150] feat: add abort signal support to inference operations --- worker/agents/inferutils/config.types.ts | 1 + worker/agents/inferutils/core.ts | 11 +++++++++++ worker/agents/inferutils/infer.ts | 9 +++++++++ 3 files changed, 21 insertions(+) diff --git a/worker/agents/inferutils/config.types.ts b/worker/agents/inferutils/config.types.ts index a943c490..a3b12007 100644 --- a/worker/agents/inferutils/config.types.ts +++ b/worker/agents/inferutils/config.types.ts @@ -85,4 +85,5 @@ export interface InferenceContext extends InferenceMetadata { userModelConfigs?: Record; enableRealtimeCodeFix: boolean; enableFastSmartCodeFix: boolean; + abortSignal?: AbortSignal; } diff --git a/worker/agents/inferutils/core.ts b/worker/agents/inferutils/core.ts index b849f0d2..ba4a2194 100644 --- a/worker/agents/inferutils/core.ts +++ b/worker/agents/inferutils/core.ts @@ -319,6 +319,7 @@ type InferArgsBase = { tools?: ToolDefinition[]; providerOverride?: 'cloudflare' | 'direct'; userApiKeys?: Record; + abortSignal?: AbortSignal; }; type InferArgsStructured = InferArgsBase & { @@ -428,6 +429,7 @@ export async function infer({ tools, reasoning_effort, temperature, + abortSignal, }: InferArgsBase & { schema?: OutputSchema; schemaName?: string; @@ -553,6 +555,7 @@ export async function infer({ reasoning_effort, temperature, }, { + signal: abortSignal, headers: { "cf-aig-metadata": JSON.stringify({ chatId: metadata.agentId, @@ -564,6 +567,12 @@ export async function infer({ }); console.log(`Inference response received`); } catch (error) { + // Check if error is due to abort + if (error instanceof Error && (error.name === 'AbortError' || error.message?.includes('aborted') || error.message?.includes('abort'))) { + console.log('Inference cancelled by user'); + throw new InferError('Inference cancelled by user', ''); + } + console.error(`Failed to get inference response from OpenAI: ${error}`); if ((error instanceof Error && error.message.includes('429')) || (typeof error === 'string' && error.includes('429'))) { throw new RateLimitExceededError('Rate limit exceeded in LLM calls, Please try again later', RateLimitType.LLM_CALLS); @@ -720,6 +729,7 @@ export async function infer({ tools, reasoning_effort, temperature, + abortSignal, }, newToolCallContext); return output; } else { @@ -734,6 +744,7 @@ export async function infer({ tools, reasoning_effort, temperature, + abortSignal, }, newToolCallContext); return output; } diff --git a/worker/agents/inferutils/infer.ts b/worker/agents/inferutils/infer.ts index 3eff3192..001cc9e0 100644 --- a/worker/agents/inferutils/infer.ts +++ b/worker/agents/inferutils/infer.ts @@ -123,6 +123,7 @@ export async function executeInference( { stream, reasoning_effort: useCheaperModel ? undefined : reasoning_effort, temperature, + abortSignal: context.abortSignal, }) : await infer({ env, metadata: context, @@ -134,6 +135,7 @@ export async function executeInference( { actionKey: agentActionName, reasoning_effort: useCheaperModel ? undefined : reasoning_effort, temperature, + abortSignal: context.abortSignal, }); logger.info(`Successfully completed ${agentActionName} operation`); // console.log(result); @@ -142,6 +144,13 @@ export async function executeInference( { if (error instanceof RateLimitExceededError || error instanceof SecurityError) { throw error; } + + // Check if cancellation - don't retry, propagate immediately + if (error instanceof InferError && error.message.includes('cancelled')) { + logger.info(`${agentActionName} operation cancelled by user, not retrying`); + throw error; + } + const isLastAttempt = attempt === retryLimit - 1; logger.error( `Error during ${agentActionName} operation (attempt ${attempt + 1}/${retryLimit}):`, From 6e752c214197ec755c988ee0308721d1f04b50fb Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 19:34:55 -0400 Subject: [PATCH 030/150] feat: add inference cancellation support with AbortController --- worker/agents/core/simpleGeneratorAgent.ts | 53 +++++++++++++++++++++- worker/agents/core/websocket.ts | 16 +++++-- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index c45d912b..77d5b5a8 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -110,6 +110,8 @@ export class SimpleCodeGeneratorAgent extends Agent { private generationPromise: Promise | null = null; private deepDebugPromise: Promise<{ transcript: string } | { error: string }> | null = null; + private currentAbortController?: AbortController; + protected operations: Operations = { codeReview: new CodeReviewOperation(), regenerateFile: new FileRegenerationOperation(), @@ -429,11 +431,60 @@ export class SimpleCodeGeneratorAgent extends Agent { agentId: this.getAgentId(), context: GenerationContext.from(this.state, this.logger()), logger: this.logger(), - inferenceContext: this.state.inferenceContext, + inferenceContext: this.getInferenceContext(), agent: this.codingAgent }; } + /** + * Creates a new abort controller for the current operation + * Aborts any previous operation in progress + */ + protected createNewAbortController(): AbortController { + // Cancel any existing operation + if (this.currentAbortController) { + this.logger().info('Aborting previous inference operation'); + this.currentAbortController.abort(); + } + + // Create new controller in memory + this.currentAbortController = new AbortController(); + + return this.currentAbortController; + } + + /** + * Cancels the current inference operation if any + */ + public cancelCurrentInference(): boolean { + if (this.currentAbortController) { + this.logger().info('Cancelling current inference operation'); + this.currentAbortController.abort(); + this.currentAbortController = undefined; + return true; + } + return false; + } + + /** + * Clears abort controller after successful completion + */ + protected clearAbortController(): void { + this.currentAbortController = undefined; + } + + /** + * Gets inference context with abort signal + */ + protected getInferenceContext(): InferenceContext { + const controller = this.createNewAbortController(); + + return { + ...this.state.inferenceContext, + abortSignal: controller.signal, + }; + } + async generateReadme() { this.logger().info('Generating README.md'); // Only generate if it doesn't exist diff --git a/worker/agents/core/websocket.ts b/worker/agents/core/websocket.ts index 0a00f3f9..6aca79fe 100644 --- a/worker/agents/core/websocket.ts +++ b/worker/agents/core/websocket.ts @@ -120,13 +120,21 @@ export function handleWebSocketMessage(agent: SimpleCodeGeneratorAgent, connecti }); break; case WebSocketMessageRequests.STOP_GENERATION: - // Clear shouldBeGenerating flag when user manually stops - logger.info('Stopping code generation and clearing shouldBeGenerating flag'); + logger.info('User requested to stop generation'); - // TODO: Implement properly + // Cancel current inference operation + const wasCancelled = agent.cancelCurrentInference(); + + // Clear shouldBeGenerating flag + agent.setState({ + ...agent.state, + shouldBeGenerating: false + }); sendToConnection(connection, WebSocketMessageResponses.GENERATION_STOPPED, { - message: 'Code generation stopped by user' + message: wasCancelled + ? 'Inference operation cancelled successfully' + : 'No active inference to cancel' }); break; case WebSocketMessageRequests.RESUME_GENERATION: From b98f86280dd802cb226dd2d7e836e6ee8b713b5e Mon Sep 17 00:00:00 2001 From: timekone <24816604+timekone@users.noreply.github.com> Date: Sat, 18 Oct 2025 02:14:16 +0200 Subject: [PATCH 031/150] fix: remove hardcoded models --- worker/agents/operations/FileRegeneration.ts | 3 +-- worker/agents/operations/PhaseImplementation.ts | 9 ++------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/worker/agents/operations/FileRegeneration.ts b/worker/agents/operations/FileRegeneration.ts index aa7031b6..a624df10 100644 --- a/worker/agents/operations/FileRegeneration.ts +++ b/worker/agents/operations/FileRegeneration.ts @@ -2,7 +2,6 @@ import { FileGenerationOutputType } from '../schemas'; import { AgentOperation, OperationOptions } from '../operations/common'; import { RealtimeCodeFixer } from '../assistants/realtimeCodeFixer'; import { FileOutputType } from '../schemas'; -import { AGENT_CONFIG } from '../inferutils/config'; export interface FileRegenerationInputs { file: FileOutputType; @@ -113,7 +112,7 @@ export class FileRegenerationOperation extends AgentOperation { try { // Use realtime code fixer to fix the file with enhanced surgical fix prompts - const realtimeCodeFixer = new RealtimeCodeFixer(options.env, options.inferenceContext, false, undefined, AGENT_CONFIG.fileRegeneration, SYSTEM_PROMPT, USER_PROMPT); + const realtimeCodeFixer = new RealtimeCodeFixer(options.env, options.inferenceContext, false, undefined, undefined, SYSTEM_PROMPT, USER_PROMPT); const fixedFile = await realtimeCodeFixer.run( inputs.file, { previousFiles: options.context.allFiles, diff --git a/worker/agents/operations/PhaseImplementation.ts b/worker/agents/operations/PhaseImplementation.ts index 00f2f073..862ec220 100644 --- a/worker/agents/operations/PhaseImplementation.ts +++ b/worker/agents/operations/PhaseImplementation.ts @@ -10,7 +10,6 @@ import { AgentOperation, getSystemPromptWithProjectContext, OperationOptions } f import { SCOFFormat, SCOFParsingState } from '../output-formats/streaming-formats/scof'; import { TemplateRegistry } from '../inferutils/schemaFormatters'; import { IsRealtimeCodeFixerEnabled, RealtimeCodeFixer } from '../assistants/realtimeCodeFixer'; -import { AGENT_CONFIG } from '../inferutils/config'; import { CodeSerializerType } from '../utils/codeSerializers'; import type { UserContext } from '../core/types'; import { imagesToBase64 } from 'worker/utils/images'; @@ -475,20 +474,16 @@ export class PhaseImplementationOperation extends AgentOperation[] = []; - let modelConfig = AGENT_CONFIG.phaseImplementation; - if (inputs.isFirstPhase) { - modelConfig = AGENT_CONFIG.firstPhaseImplementation; - } + const agentActionName = inputs.isFirstPhase ? 'firstPhaseImplementation' : 'phaseImplementation'; const shouldEnableRealtimeCodeFixer = inputs.shouldAutoFix && IsRealtimeCodeFixerEnabled(options.inferenceContext); // Execute inference with streaming await executeInference({ env: env, - agentActionName: "phaseImplementation", + agentActionName, context: options.inferenceContext, messages, - modelConfig, stream: { chunk_size: 256, onChunk: (chunk: string) => { From 3cd7cabed64f8cbd012f12f63b5be6355b9a51e4 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 21:49:56 -0400 Subject: [PATCH 032/150] feat: add stop generation button and handle cancelled state in phase timeline --- src/routes/chat/chat.tsx | 20 +++++- src/routes/chat/components/phase-timeline.tsx | 29 ++++---- src/routes/chat/hooks/use-chat.ts | 4 +- .../chat/utils/handle-websocket-message.ts | 71 ++++++++++++++----- worker/agents/core/simpleGeneratorAgent.ts | 19 ++--- 5 files changed, 100 insertions(+), 43 deletions(-) diff --git a/src/routes/chat/chat.tsx b/src/routes/chat/chat.tsx index a6479005..3a97a777 100644 --- a/src/routes/chat/chat.tsx +++ b/src/routes/chat/chat.tsx @@ -10,7 +10,7 @@ import { ArrowRight, Image as ImageIcon } from 'react-feather'; import { useParams, useSearchParams, useNavigate } from 'react-router'; import { MonacoEditor } from '../../components/monaco-editor/monaco-editor'; import { AnimatePresence, motion } from 'framer-motion'; -import { Expand, Github, LoaderCircle, RefreshCw, MoreHorizontal, RotateCcw } from 'lucide-react'; +import { Expand, Github, LoaderCircle, RefreshCw, MoreHorizontal, RotateCcw, X } from 'lucide-react'; import { Blueprint } from './components/blueprint'; import { FileExplorer } from './components/file-explorer'; import { UserMessage, AIMessage } from './components/messages'; @@ -755,6 +755,24 @@ export default function Chat() { }} />
+ {(isGenerating || isGeneratingBlueprint) && ( + + )} {/* Phase Files - Show when implementing, validating, or expanded */} - {(phase.status === 'generating' || phase.status === 'validating' || (phase.status === 'completed' && expandedPhases.has(phase.id))) && ( + {(phase.status === 'generating' || phase.status === 'validating' || ((phase.status === 'completed' || phase.status === 'cancelled') && expandedPhases.has(phase.id))) && (
{phase.files.map((phaseFile) => { // Check if this file exists in the global files array for click handling diff --git a/src/routes/chat/hooks/use-chat.ts b/src/routes/chat/hooks/use-chat.ts index 7d6d8b81..79072dc4 100644 --- a/src/routes/chat/hooks/use-chat.ts +++ b/src/routes/chat/hooks/use-chat.ts @@ -41,10 +41,10 @@ export interface PhaseTimelineItem { files: { path: string; purpose: string; - status: 'generating' | 'completed' | 'error' | 'validating'; + status: 'generating' | 'completed' | 'error' | 'validating' | 'cancelled'; contents?: string; }[]; - status: 'generating' | 'completed' | 'error' | 'validating'; + status: 'generating' | 'completed' | 'error' | 'validating' | 'cancelled'; timestamp: number; } diff --git a/src/routes/chat/utils/handle-websocket-message.ts b/src/routes/chat/utils/handle-websocket-message.ts index f726ac07..f2b5125e 100644 --- a/src/routes/chat/utils/handle-websocket-message.ts +++ b/src/routes/chat/utils/handle-websocket-message.ts @@ -187,22 +187,46 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { if (state.generatedPhases && state.generatedPhases.length > 0 && phaseTimeline.length === 0) { logger.debug('📋 Restoring phase timeline:', state.generatedPhases); - const timeline = state.generatedPhases.map((phase: any, index: number) => ({ - id: `phase-${index}`, - name: phase.name, - description: phase.description, - status: phase.completed ? 'completed' as const : 'generating' as const, - files: phase.files.map((filesConcept: any) => { - const file = state.generatedFilesMap?.[filesConcept.path]; - return { - path: filesConcept.path, - purpose: filesConcept.purpose, - status: (file ? 'completed' as const : 'generating' as const), - contents: file?.fileContents - }; - }), - timestamp: Date.now(), - })); + // If not actively generating, mark incomplete phases as cancelled (they were interrupted) + const isActivelyGenerating = state.shouldBeGenerating === true; + + const timeline = state.generatedPhases.map((phase: any, index: number) => { + // Determine phase status: + // - completed if explicitly marked complete + // - cancelled if incomplete and not actively generating (interrupted) + // - generating if incomplete and actively generating + const phaseStatus = phase.completed + ? 'completed' as const + : !isActivelyGenerating + ? 'cancelled' as const + : 'generating' as const; + + return { + id: `phase-${index}`, + name: phase.name, + description: phase.description, + status: phaseStatus, + files: phase.files.map((filesConcept: any) => { + const file = state.generatedFilesMap?.[filesConcept.path]; + // File status: + // - completed if it exists in generated files + // - cancelled if missing and not actively generating (interrupted) + // - generating if missing and actively generating + const fileStatus = file + ? 'completed' as const + : !isActivelyGenerating + ? 'cancelled' as const + : 'generating' as const; + return { + path: filesConcept.path, + purpose: filesConcept.purpose, + status: fileStatus, + contents: file?.fileContents + }; + }), + timestamp: Date.now(), + }; + }); setPhaseTimeline(timeline); } @@ -371,6 +395,7 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { case 'generation_started': { updateStage('code', { status: 'active' }); setTotalFiles(message.totalFiles); + setIsGenerating(true); // Reset review tracking for a new generation run lastReviewIssueCount = 0; reviewStartAnnounced = false; @@ -385,7 +410,11 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { updateStage('fix', { status: 'completed', metadata: undefined }); sendMessage(createAIMessage('generation-complete', 'Code generation has been completed.')); + + // Reset all phase indicators setIsPhaseProgressActive(false); + setIsThinking(false); + setIsGenerating(false); break; } @@ -594,6 +623,16 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { case 'generation_stopped': { setIsGenerating(false); setIsGenerationPaused(true); + + // Reset phase indicators + setIsPhaseProgressActive(false); + setIsThinking(false); + + // Show toast notification for user-initiated stop + toast.info('Generation stopped', { + description: message.message || 'Code generation has been stopped' + }); + sendMessage(createAIMessage('generation_stopped', message.message)); break; } diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index 77d5b5a8..952c7a0a 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -437,17 +437,16 @@ export class SimpleCodeGeneratorAgent extends Agent { } /** - * Creates a new abort controller for the current operation - * Aborts any previous operation in progress + * Gets or creates an abort controller for the current operation + * Reuses existing controller for nested operations (e.g., tool calling) */ - protected createNewAbortController(): AbortController { - // Cancel any existing operation + protected getOrCreateAbortController(): AbortController { + // Reuse existing controller if present (for nested operations) if (this.currentAbortController) { - this.logger().info('Aborting previous inference operation'); - this.currentAbortController.abort(); + return this.currentAbortController; } - // Create new controller in memory + // Create new controller in memory for new operation this.currentAbortController = new AbortController(); return this.currentAbortController; @@ -475,9 +474,10 @@ export class SimpleCodeGeneratorAgent extends Agent { /** * Gets inference context with abort signal + * Reuses existing abort controller for nested operations */ protected getInferenceContext(): InferenceContext { - const controller = this.createNewAbortController(); + const controller = this.getOrCreateAbortController(); return { ...this.state.inferenceContext, @@ -624,6 +624,9 @@ export class SimpleCodeGeneratorAgent extends Agent { error: `Error during generation: ${errorMessage}` }); } finally { + // Clear abort controller after generation completes + this.clearAbortController(); + const appService = new AppService(this.env); await appService.updateApp( this.getAgentId(), From 52d917f2f6ff39f2ea047f494cf579e28dab9a31 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 17 Oct 2025 23:48:25 -0400 Subject: [PATCH 033/150] feat: add generate_files tool for creating new files and full rewrites --- worker/agents/assistants/codeDebugger.ts | 72 +++++++++++- worker/agents/core/simpleGeneratorAgent.ts | 61 +++++++++- .../services/implementations/CodingAgent.ts | 12 +- .../services/implementations/FileManager.ts | 14 ++- .../services/interfaces/ICodingAgent.ts | 9 +- worker/agents/tools/customTools.ts | 2 + worker/agents/tools/toolkit/generate-files.ts | 106 ++++++++++++++++++ 7 files changed, 263 insertions(+), 13 deletions(-) create mode 100644 worker/agents/tools/toolkit/generate-files.ts diff --git a/worker/agents/assistants/codeDebugger.ts b/worker/agents/assistants/codeDebugger.ts index 30c9a0f1..1aa6ca7e 100644 --- a/worker/agents/assistants/codeDebugger.ts +++ b/worker/agents/assistants/codeDebugger.ts @@ -89,7 +89,8 @@ You are methodical and evidence-based. You choose your own path to solve issues, - **get_logs**: Cumulative logs (verbose, user-driven). **Use sparingly** - only when runtime errors lack detail. Set reset=true to clear stale logs. - **read_files**: Read file contents by RELATIVE paths (batch multiple in one call for efficiency) - **exec_commands**: Execute shell commands from project root (no cd needed) -- **regenerate_file**: Autonomous surgical code fixer - see detailed guide below +- **regenerate_file**: Autonomous surgical code fixer for existing files - see detailed guide below +- **generate_files**: Generate new files or rewrite broken files using phase implementation - see detailed guide below - **deploy_preview**: Deploy to Cloudflare Workers preview environment to verify fixes - **wait**: Sleep for N seconds (use after deploy to allow time for user interaction before checking logs) @@ -181,11 +182,78 @@ regenerate_file({ path: "src/App.tsx", issues: ["Fix error B"] }) - ✅ API endpoint mismatches **When NOT to use regenerate_file:** -- ❌ Files that don't exist yet (file must exist first) +- ❌ Files that don't exist yet (use generate_files instead) - ❌ wrangler.jsonc or package.json (these are locked) - ❌ Configuration issues that need different tools - ❌ When you haven't read the file yet (read it first!) - ❌ When the same issue has already been fixed (check diff!) +- ❌ When file is too broken to patch (use generate_files to rewrite) + +## How to Use generate_files (For New/Broken Files) + +**What it is:** +- Generates complete new files or rewrites existing files using full phase implementation +- Use when regenerate_file fails repeatedly or file doesn't exist +- Automatically determines file contents based on requirements +- Deploys changes to sandbox +- Returns diffs for all generated files + +**When to use generate_files:** +- ✅ File doesn't exist yet (need to create it) +- ✅ regenerate_file failed 2+ times (file too broken to patch) +- ✅ Need multiple coordinated files for a feature +- ✅ Scaffolding new components/utilities/API routes + +**When NOT to use generate_files:** +- ❌ Use regenerate_file first for existing files with fixable issues (it's faster and more surgical) +- ❌ Don't use for simple fixes - regenerate_file is better + +**Parameters:** +\`\`\`typescript +generate_files({ + phase_name: "Add data export utilities", + phase_description: "Create helper functions for exporting data as CSV/JSON", + requirements: [ + "Create src/utils/exportHelpers.ts with exportToCSV(data: any[], filename: string) function", + "Create src/utils/exportHelpers.ts with exportToJSON(data: any[], filename: string) function", + "Add proper TypeScript types for all export functions", + "Functions should trigger browser download with the given filename" + ], + files: [ + { + path: "src/utils/exportHelpers.ts", + purpose: "Data export utility functions for CSV and JSON formats", + changes: null // null for new files, or description of changes for existing files + } + ] +}) +\`\`\` + +**CRITICAL - Requirements Must Be Detailed:** +- ✅ Be EXTREMELY specific: function signatures, types, implementation details +- ✅ Include file paths explicitly in requirements +- ✅ Specify exact behavior, edge cases, error handling +- ❌ Don't be vague: "add utilities" is BAD, "create exportToCSV function that takes array and filename" is GOOD + +**What generate_files returns:** +\`\`\`typescript +{ + files: [ + { + path: "src/utils/exportHelpers.ts", + purpose: "Data export utility functions", + diff: "Complete unified diff showing all changes" + } + ], + summary: "Generated 1 file(s) for: Add data export utilities" +} +\`\`\` + +**Strategy:** +1. Try regenerate_file FIRST for existing files +2. If regenerate_file fails 2+ times → use generate_files to rewrite +3. For new files that don't exist → use generate_files directly +4. Review the diffs returned - they show exactly what was generated ## File Path Rules (CRITICAL) - All paths are RELATIVE to project root (sandbox pwd = project directory) diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index 952c7a0a..e065f684 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -3,6 +3,7 @@ import { Blueprint, PhaseConceptGenerationSchemaType, PhaseConceptType, + FileConceptType, FileOutputType, PhaseImplementationSchemaType, } from '../schemas'; @@ -813,7 +814,7 @@ export class SimpleCodeGeneratorAgent extends Agent { const fileToRegenerate = this.fileManager.getGeneratedFile(fileToFix.filePath); if (!fileToRegenerate) { - this.logger().warn(`File to fix not found in generated files: ${fileToFix.filePath}`); + this.logger().warn(`File to fix not found in generated files: ${fileToFix.filePath}, skipping`); continue; } @@ -1030,7 +1031,7 @@ export class SimpleCodeGeneratorAgent extends Agent { * Implement a single phase of code generation * Streams file generation with real-time updates and incorporates technical instructions */ - async implementPhase(phase: PhaseConceptType, currentIssues: AllIssues, userContext?: UserContext, streamChunks: boolean = true): Promise { + async implementPhase(phase: PhaseConceptType, currentIssues: AllIssues, userContext?: UserContext, streamChunks: boolean = true, postPhaseFixing: boolean = true): Promise { const issues = IssueReport.from(currentIssues); const implementationMsg = userContext?.suggestions && userContext.suggestions.length > 0 @@ -1109,9 +1110,11 @@ export class SimpleCodeGeneratorAgent extends Agent { // Deploy generated files if (finalFiles.length > 0) { await this.deployToSandbox(finalFiles, false, phase.name); - await this.applyDeterministicCodeFixes(); - if (this.state.inferenceContext.enableFastSmartCodeFix) { - await this.applyFastSmartCodeFixes(); + if (postPhaseFixing) { + await this.applyDeterministicCodeFixes(); + if (this.state.inferenceContext.enableFastSmartCodeFix) { + await this.applyFastSmartCodeFixes(); + } } } @@ -1867,6 +1870,54 @@ export class SimpleCodeGeneratorAgent extends Agent { return { path, diff: regenerated.lastDiff }; } + async generateFiles( + phaseName: string, + phaseDescription: string, + requirements: string[], + files: FileConceptType[] + ): Promise<{ files: Array<{ path: string; purpose: string; diff: string }> }> { + this.logger().info('Generating files for deep debugger', { + phaseName, + requirementsCount: requirements.length, + filesCount: files.length + }); + + // Create phase structure with explicit files + const phase: PhaseConceptType = { + name: phaseName, + description: phaseDescription, + files: files, + lastPhase: true + }; + + // Call existing implementPhase with postPhaseFixing=false + // This skips deterministic fixes and fast smart fixes + const result = await this.implementPhase( + phase, + { + runtimeErrors: [], + staticAnalysis: { + success: true, + lint: { issues: [] }, + typecheck: { issues: [] } + }, + clientErrors: [] + }, + { suggestions: requirements }, + true, // streamChunks + false // postPhaseFixing = false (skip auto-fixes) + ); + + // Return files with diffs from FileState + return { + files: result.files.map(f => ({ + path: f.filePath, + purpose: f.filePurpose || '', + diff: (f as any).lastDiff || '' // FileState has lastDiff + })) + }; + } + async waitForPreview(): Promise { this.logger().info("Waiting for preview"); if (!this.state.sandboxInstanceId) { diff --git a/worker/agents/services/implementations/CodingAgent.ts b/worker/agents/services/implementations/CodingAgent.ts index 6e980d71..978f2835 100644 --- a/worker/agents/services/implementations/CodingAgent.ts +++ b/worker/agents/services/implementations/CodingAgent.ts @@ -1,5 +1,5 @@ import { ProcessedImageAttachment } from "worker/types/image-attachment"; -import { Blueprint } from "worker/agents/schemas"; +import { Blueprint, FileConceptType } from "worker/agents/schemas"; import { ExecuteCommandsResponse, StaticAnalysisResponse, RuntimeError } from "worker/services/sandbox/sandboxTypes"; import { ICodingAgent } from "../interfaces/ICodingAgent"; import { OperationOptions } from "worker/agents/operations/common"; @@ -82,6 +82,16 @@ export class CodingAgentInterface { return this.agentStub.regenerateFileByPath(path, issues); } + // Exposes file generation via phase implementation + generateFiles( + phaseName: string, + phaseDescription: string, + requirements: string[], + files: FileConceptType[] + ): Promise<{ files: Array<{ path: string; purpose: string; diff: string }> }> { + return this.agentStub.generateFiles(phaseName, phaseDescription, requirements, files); + } + isCodeGenerating(): boolean { return this.agentStub.isCodeGenerating(); } diff --git a/worker/agents/services/implementations/FileManager.ts b/worker/agents/services/implementations/FileManager.ts index a16ac6d4..3107af1a 100644 --- a/worker/agents/services/implementations/FileManager.ts +++ b/worker/agents/services/implementations/FileManager.ts @@ -42,17 +42,23 @@ export class FileManager implements IFileManager { for (const file of files) { let lastDiff = ''; const oldFile = filesMap[file.filePath]; - if (oldFile) { + + // Get comparison base: from generatedFilesMap, template/filesystem, or empty string for new files + const oldFileContents = oldFile?.fileContents ?? (this.getFileContents(file.filePath) || ''); + + // Generate diff if contents changed + if (oldFileContents !== file.fileContents) { try { - // Generate diff of old file and new file - lastDiff = Diff.createPatch(file.filePath, oldFile.fileContents, file.fileContents); + lastDiff = Diff.createPatch(file.filePath, oldFileContents, file.fileContents); if (lastDiff) { - console.log(`Generated diff for file ${file.filePath}:`, lastDiff); + const isNewFile = oldFileContents === ''; + console.log(`Generated diff for ${isNewFile ? 'new' : ''} file ${file.filePath}:`, lastDiff); } } catch (error) { console.error(`Failed to generate diff for file ${file.filePath}:`, error); } } + const fileState = { ...file, lasthash: '', diff --git a/worker/agents/services/interfaces/ICodingAgent.ts b/worker/agents/services/interfaces/ICodingAgent.ts index d297d1bc..e71a331b 100644 --- a/worker/agents/services/interfaces/ICodingAgent.ts +++ b/worker/agents/services/interfaces/ICodingAgent.ts @@ -1,4 +1,4 @@ -import { FileOutputType, Blueprint } from "worker/agents/schemas"; +import { FileOutputType, Blueprint, FileConceptType } from "worker/agents/schemas"; import { BaseSandboxService } from "worker/services/sandbox/BaseSandboxService"; import { ExecuteCommandsResponse, PreviewType, StaticAnalysisResponse, RuntimeError } from "worker/services/sandbox/sandboxTypes"; import { ProcessedImageAttachment } from "worker/types/image-attachment"; @@ -34,6 +34,13 @@ export abstract class ICodingAgent { abstract regenerateFileByPath(path: string, issues: string[]): Promise<{ path: string; diff: string }>; + abstract generateFiles( + phaseName: string, + phaseDescription: string, + requirements: string[], + files: FileConceptType[] + ): Promise<{ files: Array<{ path: string; purpose: string; diff: string }> }>; + abstract fetchRuntimeErrors(clear?: boolean): Promise; abstract isCodeGenerating(): boolean; diff --git a/worker/agents/tools/customTools.ts b/worker/agents/tools/customTools.ts index cca8a3e3..a478f7f1 100644 --- a/worker/agents/tools/customTools.ts +++ b/worker/agents/tools/customTools.ts @@ -15,6 +15,7 @@ import { createReadFilesTool } from './toolkit/read-files'; import { createExecCommandsTool } from './toolkit/exec-commands'; import { createRunAnalysisTool } from './toolkit/run-analysis'; import { createRegenerateFileTool } from './toolkit/regenerate-file'; +import { createGenerateFilesTool } from './toolkit/generate-files'; import { createWaitTool } from './toolkit/wait'; import { createGetRuntimeErrorsTool } from './toolkit/get-runtime-errors'; import { createWaitForGenerationTool } from './toolkit/wait-for-generation'; @@ -63,6 +64,7 @@ export function buildDebugTools(session: DebugSession, logger: StructuredLogger, createRunAnalysisTool(session.agent, logger), createExecCommandsTool(session.agent, logger), createRegenerateFileTool(session.agent, logger), + createGenerateFilesTool(session.agent, logger), createDeployPreviewTool(session.agent, logger), createWaitTool(logger), ]; diff --git a/worker/agents/tools/toolkit/generate-files.ts b/worker/agents/tools/toolkit/generate-files.ts new file mode 100644 index 00000000..7091a818 --- /dev/null +++ b/worker/agents/tools/toolkit/generate-files.ts @@ -0,0 +1,106 @@ +import { ToolDefinition, ErrorResult } from '../types'; +import { StructuredLogger } from '../../../logger'; +import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; +import { FileConceptType } from 'worker/agents/schemas'; + +export type GenerateFilesArgs = { + phase_name: string; + phase_description: string; + requirements: string[]; + files: FileConceptType[]; +}; + +export type GenerateFilesResult = + | { + files: Array<{ path: string; purpose: string; diff: string }>; + summary: string; + } + | ErrorResult; + +export function createGenerateFilesTool( + agent: CodingAgentInterface, + logger: StructuredLogger, +): ToolDefinition { + return { + type: 'function' as const, + function: { + name: 'generate_files', + description: `Generate new files or completely rewrite existing files using the full phase implementation system. + +Use this when: +- File(s) don't exist and need to be created +- regenerate_file failed (file too broken to patch) +- Need multiple coordinated files for a feature +- Scaffolding new components/utilities + +The system will: +1. Automatically determine which files to create based on requirements +2. Generate properly typed, coordinated code +3. Deploy changes to sandbox +4. Return diffs for all generated files + +Provide detailed, specific requirements. The more detail, the better the results.`, + parameters: { + type: 'object', + properties: { + phase_name: { + type: 'string', + description: + 'Short, descriptive name for what you\'re generating (e.g., "Add data export utilities")', + }, + phase_description: { + type: 'string', + description: 'Brief description of what these files should accomplish', + }, + requirements: { + type: 'array', + items: { type: 'string' }, + description: + 'Array of specific, detailed requirements. Be explicit about function signatures, types, implementation details.', + }, + files: { + type: 'array', + items: { + type: 'object', + properties: { + path: { type: 'string', description: 'File path relative to project root' }, + purpose: { type: 'string', description: 'Brief description of file purpose' }, + changes: { type: ['string', 'null'], description: 'Specific changes for existing files, or null for new files' } + }, + required: ['path', 'purpose', 'changes'] + }, + description: 'Array of files to generate with their paths and purposes' + }, + }, + required: ['phase_name', 'phase_description', 'requirements', 'files'], + }, + }, + implementation: async ({ phase_name, phase_description, requirements, files }) => { + try { + logger.info('Generating files via phase implementation', { + phase_name, + requirementsCount: requirements.length, + filesCount: files.length, + }); + + const result = await agent.generateFiles(phase_name, phase_description, requirements, files); + + return { + files: result.files.map((f) => ({ + path: f.path, + purpose: f.purpose || '', + diff: f.diff, + })), + summary: `Generated ${result.files.length} file(s) for: ${phase_name}`, + }; + } catch (error) { + return { + error: + error instanceof Error + ? `Failed to generate files: ${error.message}` + : 'Unknown error occurred while generating files', + }; + } + }, + }; +} From 1365eebae9fc150a91d048bae2e6b5cf11cda350 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sat, 18 Oct 2025 00:22:35 -0400 Subject: [PATCH 034/150] feat: display completion message when entering finalization phase --- src/routes/chat/utils/handle-websocket-message.ts | 4 ++++ src/routes/chat/utils/message-helpers.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/routes/chat/utils/handle-websocket-message.ts b/src/routes/chat/utils/handle-websocket-message.ts index f2b5125e..97abe729 100644 --- a/src/routes/chat/utils/handle-websocket-message.ts +++ b/src/routes/chat/utils/handle-websocket-message.ts @@ -592,6 +592,10 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { } return updated; }); + + if (message.phase.name === 'Finalization and Review') { + sendMessage(createAIMessage('core_app_complete', 'Main app generation completed. Doing code cleanups and resolving any lingering issues. Meanwhile, feel free to ask me anything!')); + } } logger.debug('🔄 Scheduling preview refresh in 1 second after deployment completion'); diff --git a/src/routes/chat/utils/message-helpers.ts b/src/routes/chat/utils/message-helpers.ts index 5f581099..7c0e461b 100644 --- a/src/routes/chat/utils/message-helpers.ts +++ b/src/routes/chat/utils/message-helpers.ts @@ -32,6 +32,8 @@ export function isConversationalMessage(messageId: string): boolean { 'chat-welcome', 'deployment-status', 'code_reviewed', + 'generation-complete', + 'core_app_complete', ]; return conversationalIds.includes(messageId) || messageId.startsWith('conv-'); From 1259176c3b77500c4d145207f33d4a29cad20d74 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sat, 18 Oct 2025 00:22:54 -0400 Subject: [PATCH 035/150] fix: handle missing sandbox instance by returning empty issues object --- worker/agents/core/simpleGeneratorAgent.ts | 25 ++++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index e065f684..2cb4c644 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -739,17 +739,24 @@ export class SimpleCodeGeneratorAgent extends Agent { }); let currentIssues : AllIssues; - if (staticAnalysis) { - // If have cached static analysis, fetch everything else fresh - currentIssues = { - runtimeErrors: await this.fetchRuntimeErrors(true), - staticAnalysis: staticAnalysis, - clientErrors: this.state.clientReportedErrors - }; + if (this.state.sandboxInstanceId) { + if (staticAnalysis) { + // If have cached static analysis, fetch everything else fresh + currentIssues = { + runtimeErrors: await this.fetchRuntimeErrors(true), + staticAnalysis: staticAnalysis, + clientErrors: this.state.clientReportedErrors + }; + } else { + currentIssues = await this.fetchAllIssues(true) + } } else { - currentIssues = await this.fetchAllIssues(true) + currentIssues = { + runtimeErrors: [], + staticAnalysis: { success: true, lint: { issues: [] }, typecheck: { issues: [] } }, + clientErrors: [] + } } - // Implement the phase with user context (suggestions and images) await this.implementPhase(phaseConcept, currentIssues, userContext); From 989aa1bf9c649558bd4aa959c906cf438be29ef6 Mon Sep 17 00:00:00 2001 From: timekone <24816604+timekone@users.noreply.github.com> Date: Sat, 18 Oct 2025 17:23:50 +0200 Subject: [PATCH 036/150] fix: fileRegeneration was using realtimeCodeFixer's model config --- worker/agents/assistants/realtimeCodeFixer.ts | 11 +++++------ worker/agents/operations/FileRegeneration.ts | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/worker/agents/assistants/realtimeCodeFixer.ts b/worker/agents/assistants/realtimeCodeFixer.ts index a62c33ed..809caa72 100644 --- a/worker/agents/assistants/realtimeCodeFixer.ts +++ b/worker/agents/assistants/realtimeCodeFixer.ts @@ -9,7 +9,7 @@ import Assistant from "./assistant"; import { applySearchReplaceDiff } from "../output-formats/diff-formats"; import { infer } from "../inferutils/core"; import { MatchingStrategy, FailedBlock } from "../output-formats/diff-formats/search-replace"; -import { AIModels, ModelConfig, InferenceContext } from "../inferutils/config.types"; +import { AgentActionKey, AIModels, InferenceContext } from "../inferutils/config.types"; import { AGENT_CONFIG } from "../inferutils/config"; // import { analyzeTypeScriptFile } from "../../services/code-fixer/analyzer"; @@ -212,23 +212,23 @@ export class RealtimeCodeFixer extends Assistant { altPassModelOverride?: string; userPrompt: string; systemPrompt: string; - modelConfigOverride?: ModelConfig; + agentActionNameOverride?: AgentActionKey; constructor( env: Env, inferenceContext: InferenceContext, lightMode: boolean = false, altPassModelOverride?: string,// = AIModels.GEMINI_2_5_FLASH, - modelConfigOverride?: ModelConfig, + agentActionNameOverride?: AgentActionKey, systemPrompt: string = SYSTEM_PROMPT, userPrompt: string = USER_PROMPT ) { super(env, inferenceContext); this.lightMode = lightMode; this.altPassModelOverride = altPassModelOverride; + this.agentActionNameOverride = agentActionNameOverride; this.userPrompt = userPrompt; this.systemPrompt = systemPrompt; - this.modelConfigOverride = modelConfigOverride; } async run( @@ -284,13 +284,12 @@ Don't be nitpicky, If there are no actual issues, just say "No issues found". const fixResult = await executeInference({ env: this.env, - agentActionName: "realtimeCodeFixer", + agentActionName: this.agentActionNameOverride ?? "realtimeCodeFixer", context: this.inferenceContext, messages, modelName: (i !== 0 && this.altPassModelOverride) || this.lightMode ? this.altPassModelOverride : undefined, temperature: (i !== 0 && this.altPassModelOverride) || this.lightMode ? 0.0 : undefined, reasoning_effort: (i !== 0 && this.altPassModelOverride) || this.lightMode ? 'low' : undefined, - modelConfig: this.modelConfigOverride, }); if (!fixResult) { diff --git a/worker/agents/operations/FileRegeneration.ts b/worker/agents/operations/FileRegeneration.ts index a624df10..a79770ee 100644 --- a/worker/agents/operations/FileRegeneration.ts +++ b/worker/agents/operations/FileRegeneration.ts @@ -112,7 +112,7 @@ export class FileRegenerationOperation extends AgentOperation { try { // Use realtime code fixer to fix the file with enhanced surgical fix prompts - const realtimeCodeFixer = new RealtimeCodeFixer(options.env, options.inferenceContext, false, undefined, undefined, SYSTEM_PROMPT, USER_PROMPT); + const realtimeCodeFixer = new RealtimeCodeFixer(options.env, options.inferenceContext, false, undefined, "fileRegeneration", SYSTEM_PROMPT, USER_PROMPT); const fixedFile = await realtimeCodeFixer.run( inputs.file, { previousFiles: options.context.allFiles, From 426eb73e1d329a4d59c5ed49390b626b420f24ea Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sat, 18 Oct 2025 12:58:22 -0400 Subject: [PATCH 037/150] refactor: extract state migration logic and general refactors --- worker/agents/core/simpleGeneratorAgent.ts | 516 ++++++++------------- worker/agents/core/stateMigration.ts | 177 +++++++ 2 files changed, 369 insertions(+), 324 deletions(-) create mode 100644 worker/agents/core/stateMigration.ts diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index 2cb4c644..d297f794 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -9,7 +9,7 @@ import { } from '../schemas'; import { GitHubPushRequest, PreviewType, StaticAnalysisResponse, TemplateDetails } from '../../services/sandbox/sandboxTypes'; import { GitHubExportResult } from '../../services/github/types'; -import { CodeGenState, CurrentDevState, MAX_PHASES, FileState } from './state'; +import { CodeGenState, CurrentDevState, MAX_PHASES } from './state'; import { AllIssues, AgentSummary, AgentInitArgs, PhaseExecutionResult, UserContext } from './types'; import { MAX_DEPLOYMENT_RETRIES, PREVIEW_EXPIRED_ERROR, WebSocketMessageResponses } from '../constants'; import { broadcastToConnections, handleWebSocketClose, handleWebSocketMessage } from './websocket'; @@ -50,6 +50,7 @@ import { ImageType, uploadImage } from 'worker/utils/images'; import { ConversationMessage, ConversationState } from '../inferutils/common'; import { DeepCodeDebugger } from '../assistants/codeDebugger'; import { DeepDebugResult } from './types'; +import { StateMigration } from './stateMigration'; interface WebhookPayload { event: { @@ -96,6 +97,13 @@ const DEFAULT_CONVERSATION_SESSION_ID = 'default'; * - Deployment to sandbox service */ export class SimpleCodeGeneratorAgent extends Agent { + private static readonly DEPLOYMENT_TIMEOUT_MS = 60_000; + private static readonly COMMAND_TIMEOUT_MS = 60_000; + private static readonly HEALTH_CHECK_INTERVAL_MS = 5_000; + private static readonly MAX_COMMANDS_HISTORY = 50; + private static readonly PROJECT_NAME_PREFIX_MAX_LENGTH = 20; + private static readonly MAX_HEALTH_CHECK_FAILURES = 3; + protected projectSetupAssistant: ProjectSetupAssistant | undefined; protected sandboxServiceClient: BaseSandboxService | undefined; protected fileManager: FileManager = new FileManager( @@ -112,6 +120,7 @@ export class SimpleCodeGeneratorAgent extends Agent { private deepDebugPromise: Promise<{ transcript: string } | { error: string }> | null = null; private currentAbortController?: AbortController; + private healthCheckFailures = 0; protected operations: Operations = { codeReview: new CodeReviewOperation(), @@ -313,29 +322,30 @@ export class SimpleCodeGeneratorAgent extends Agent { inferenceContext, }); - try { - // Deploy to sandbox service and generate initial setup commands in parallel - Promise.all([this.deployToSandbox(), this.getProjectSetupAssistant().generateSetupCommands(), this.generateReadme()]).then(async ([, setupCommands, _readme]) => { - this.logger().info("Deployment to sandbox service and initial commands predictions completed successfully"); - await this.executeCommands(setupCommands.commands); - this.logger().info("Initial commands executed successfully"); - }).catch(error => { - this.logger().error("Error during deployment:", error); - this.broadcast(WebSocketMessageResponses.ERROR, { - error: `Error during deployment: ${error instanceof Error ? error.message : String(error)}` - }); - }); - } catch (error) { - this.logger().error("Error during deployment:", error); - this.broadcast(WebSocketMessageResponses.ERROR, { - error: `Error during deployment: ${error instanceof Error ? error.message : String(error)}` - }); - } + this.initializeAsync().catch((error: unknown) => { + this.broadcastError("Initialization failed", error); + }); this.logger().info(`Agent ${this.getAgentId()} session: ${this.state.sessionId} initialized successfully`); await this.saveToDatabase(); return this.state; } + private async initializeAsync(): Promise { + try { + const [, setupCommands] = await Promise.all([ + this.deployToSandbox(), + this.getProjectSetupAssistant().generateSetupCommands(), + this.generateReadme() + ]); + this.logger().info("Deployment to sandbox service and initial commands predictions completed successfully"); + await this.executeCommands(setupCommands.commands); + this.logger().info("Initial commands executed successfully"); + } catch (error) { + this.logger().error("Error during async initialization:", error); + throw error; + } + } + async isInitialized() { return this.getAgentId() ? true : false } @@ -486,6 +496,45 @@ export class SimpleCodeGeneratorAgent extends Agent { }; } + private async withTimeout( + operation: Promise, + timeoutMs: number, + errorMsg: string, + onTimeout?: () => void + ): Promise { + return Promise.race([ + operation, + new Promise((_, reject) => + setTimeout(() => { + onTimeout?.(); + reject(new Error(errorMsg)); + }, timeoutMs) + ) + ]); + } + + private broadcastError(context: string, error: unknown): void { + const errorMessage = error instanceof Error ? error.message : String(error); + this.logger().error(`${context}:`, error); + this.broadcast(WebSocketMessageResponses.ERROR, { + error: `${context}: ${errorMessage}` + }); + } + + private getFilesToDeploy( + requestedFiles: FileOutputType[], + redeployed: boolean + ): Array<{ filePath: string; fileContents: string }> { + if (!requestedFiles || requestedFiles.length === 0 || redeployed) { + requestedFiles = Object.values(this.state.generatedFilesMap) + } + + return requestedFiles.map(file => ({ + filePath: file.filePath, + fileContents: file.fileContents + })); + } + async generateReadme() { this.logger().info('Generating README.md'); // Only generate if it doesn't exist @@ -1310,198 +1359,11 @@ export class SimpleCodeGeneratorAgent extends Agent { return this.state; } - /** - * Migrate old snake_case file properties to camelCase format - * This is needed for apps created before the schema migration - */ private migrateStateIfNeeded(): void { - let needsMigration = false; - - // Helper function to migrate a file object from snake_case to camelCase - const migrateFile = (file: any): any => { - const hasOldFormat = 'file_path' in file || 'file_contents' in file || 'file_purpose' in file; - - if (hasOldFormat) { - return { - filePath: file.filePath || file.file_path, - fileContents: file.fileContents || file.file_contents, - filePurpose: file.filePurpose || file.file_purpose, - }; - } - return file; - }; - - // Migrate generatedFilesMap - const migratedFilesMap: Record = {}; - for (const [key, file] of Object.entries(this.state.generatedFilesMap)) { - const migratedFile = migrateFile(file); - - // Add FileState-specific properties if missing - migratedFilesMap[key] = { - ...migratedFile, - lasthash: migratedFile.lasthash || '', - lastmodified: migratedFile.lastmodified || Date.now(), - unmerged: migratedFile.unmerged || [] - }; - - if (migratedFile !== file) { - needsMigration = true; - } + const migratedState = StateMigration.migrateIfNeeded(this.state, this.logger()); + if (migratedState) { + this.setState(migratedState); } - - // Migrate templateDetails.files - let migratedTemplateDetails = this.state.templateDetails; - if (migratedTemplateDetails?.files) { - const migratedTemplateFiles = migratedTemplateDetails.files.map(file => { - const migratedFile = migrateFile(file); - if (migratedFile !== file) { - needsMigration = true; - } - return migratedFile; - }); - - if (needsMigration) { - migratedTemplateDetails = { - ...migratedTemplateDetails, - files: migratedTemplateFiles - }; - } - } - - // Fix conversation message exponential bloat caused by incorrect message accumulation - let migratedConversationMessages = this.state.conversationMessages; - const MIN_MESSAGES_FOR_CLEANUP = 25; - - if (migratedConversationMessages && migratedConversationMessages.length > 0) { - const originalCount = migratedConversationMessages.length; - - // Deduplicate messages by conversationId - const seen = new Set(); - const uniqueMessages = []; - - for (const message of migratedConversationMessages) { - // Use conversationId as primary unique key since it should be unique per message - let key = message.conversationId; - if (!key) { - // Fallback for messages without conversationId - const contentStr = typeof message.content === 'string' - ? message.content.substring(0, 100) - : JSON.stringify(message.content || '').substring(0, 100); - key = `${message.role || 'unknown'}_${contentStr}_${Date.now()}`; - } - - if (!seen.has(key)) { - seen.add(key); - uniqueMessages.push(message); - } - } - - // Sort messages by timestamp (extracted from conversationId) to maintain chronological order - uniqueMessages.sort((a, b) => { - const getTimestamp = (msg: any) => { - if (msg.conversationId && typeof msg.conversationId === 'string' && msg.conversationId.startsWith('conv-')) { - const parts = msg.conversationId.split('-'); - if (parts.length >= 2) { - return parseInt(parts[1]) || 0; - } - } - return 0; - }; - return getTimestamp(a) - getTimestamp(b); - }); - - // Smart filtering: if we have more than MIN_MESSAGES_FOR_CLEANUP, remove internal memos but keep actual conversations - if (uniqueMessages.length > MIN_MESSAGES_FOR_CLEANUP) { - const realConversations = []; - const internalMemos = []; - - for (const message of uniqueMessages) { - const content = typeof message.content === 'string' ? message.content : JSON.stringify(message.content || ''); - const isInternalMemo = content.includes('****') || content.includes('Project Updates:'); - - if (isInternalMemo) { - internalMemos.push(message); - } else { - realConversations.push(message); - } - } - - this.logger().info('Conversation cleanup analysis', { - totalUniqueMessages: uniqueMessages.length, - realConversations: realConversations.length, - internalMemos: internalMemos.length, - willRemoveInternalMemos: uniqueMessages.length > MIN_MESSAGES_FOR_CLEANUP - }); - - // Keep all real conversations, remove internal memos if we exceed the threshold - migratedConversationMessages = realConversations; - } else { - // If we have few messages, keep everything - migratedConversationMessages = uniqueMessages; - } - - if (migratedConversationMessages.length !== originalCount) { - this.logger().info('Fixed conversation message exponential bloat', { - originalCount, - deduplicatedCount: uniqueMessages.length, - finalCount: migratedConversationMessages.length, - duplicatesRemoved: originalCount - uniqueMessages.length, - internalMemosRemoved: uniqueMessages.length - migratedConversationMessages.length - }); - needsMigration = true; - } - } - - let migratedInferenceContext = this.state.inferenceContext; - if (migratedInferenceContext && 'userApiKeys' in migratedInferenceContext) { - migratedInferenceContext = { - ...migratedInferenceContext - }; - - // Completely remove the userApiKeys property for security - delete (migratedInferenceContext as any).userApiKeys; - needsMigration = true; - } - - // Check for deprecated properties - const stateHasDeprecatedProps = 'latestScreenshot' in (this.state as any); - if (stateHasDeprecatedProps) { - needsMigration = true; - } - - // Check if projectUpdatesAccumulator is not in state - const stateHasProjectUpdatesAccumulator = 'projectUpdatesAccumulator' in (this.state as any); - if (!stateHasProjectUpdatesAccumulator) { - needsMigration = true; - } - - // Apply migration if needed - if (needsMigration) { - this.logger().info('Migrating state: schema format, conversation cleanup, and security fixes', { - generatedFilesCount: Object.keys(migratedFilesMap).length, - templateFilesCount: migratedTemplateDetails?.files?.length || 0, - finalConversationCount: migratedConversationMessages?.length || 0, - removedUserApiKeys: this.state.inferenceContext && 'userApiKeys' in this.state.inferenceContext - }); - - const newState = { - ...this.state, - generatedFilesMap: migratedFilesMap, - templateDetails: migratedTemplateDetails, - conversationMessages: migratedConversationMessages, - inferenceContext: migratedInferenceContext, - projectUpdatesAccumulator: [] - }; - - // Remove deprecated properties - if (stateHasDeprecatedProps) { - delete (newState as any).latestScreenshot; - } - - this.setState(newState); - } - - } getFileGenerated(filePath: string) { @@ -1956,29 +1818,20 @@ export class SimpleCodeGeneratorAgent extends Agent { this.logger().info("Deploying to sandbox", { files, redeploy, commitMessage, sessionId: this.state.sessionId }); - // Start the actual deployment and track it this.currentDeploymentPromise = this.executeDeployment(files, redeploy, commitMessage, clearLogs); - // Create timeout that resets session if deployment hangs - let timeoutId: ReturnType | null = null; - const timeoutPromise = new Promise((_, reject) => { - timeoutId = setTimeout(() => { - this.logger().warn('Deployment timed out after 60 seconds, resetting sessionId to provision new sandbox instance'); - this.resetSessionId(); - reject(new Error('Deployment timed out after 60 seconds')); - }, 60000); - }); - try { - const result = await Promise.race([ + const result = await this.withTimeout( this.currentDeploymentPromise, - timeoutPromise - ]); + SimpleCodeGeneratorAgent.DEPLOYMENT_TIMEOUT_MS, + 'Deployment timed out', + () => { + this.logger().warn('Deployment timed out, resetting sessionId to provision new sandbox instance'); + this.resetSessionId(); + } + ); return result; } finally { - if (timeoutId !== null) { - clearTimeout(timeoutId); - } this.currentDeploymentPromise = null; } } @@ -1989,8 +1842,7 @@ export class SimpleCodeGeneratorAgent extends Agent { // Generate a unique suffix let prefix = (this.state.blueprint?.projectName || templateName).toLowerCase().replace(/[^a-z0-9]/g, '-'); const uniqueSuffix = generateId(); - // Only use the first 20 characters of the prefix - prefix = prefix.slice(0, 20); + prefix = prefix.slice(0, SimpleCodeGeneratorAgent.PROJECT_NAME_PREFIX_MAX_LENGTH); const projectName = `${prefix}-${uniqueSuffix}`.toLowerCase(); // Generate webhook URL for this agent instance @@ -2020,107 +1872,132 @@ export class SimpleCodeGeneratorAgent extends Agent { throw new Error(`Failed to create sandbox instance: ${createResponse?.error || 'Unknown error'}`); } - private async ensurePreviewExists(redeploy: boolean = false) { - let { sandboxInstanceId } = this.state; - let previewURL: string | undefined; - let tunnelURL: string | undefined; - let redeployed = false; + private async getOrCreateInstance(redeploy: boolean): Promise<{ + sandboxInstanceId: string; + previewURL: string | undefined; + tunnelURL: string | undefined; + redeployed: boolean; + }> { + const { sandboxInstanceId } = this.state; - // Check if the instance is running - if (sandboxInstanceId) { + if (sandboxInstanceId && !redeploy) { const status = await this.getSandboxServiceClient().getInstanceStatus(sandboxInstanceId); - if (!status.success || !status.isHealthy) { - this.logger().error(`DEPLOYMENT CHECK FAILED: Failed to get status for instance ${sandboxInstanceId}, redeploying...`); - sandboxInstanceId = undefined; - } else { - this.logger().info(`DEPLOYMENT CHECK PASSED: Instance ${sandboxInstanceId} is running, previewURL: ${status.previewURL}, tunnelURL: ${status.tunnelURL}`); - previewURL = status.previewURL; - tunnelURL = status.tunnelURL; + if (status.success && status.isHealthy) { + this.logger().info(`DEPLOYMENT CHECK PASSED: Instance ${sandboxInstanceId} is running`); + return { + sandboxInstanceId, + previewURL: status.previewURL, + tunnelURL: status.tunnelURL, + redeployed: false + }; } + this.logger().error(`DEPLOYMENT CHECK FAILED: Failed to get status for instance ${sandboxInstanceId}, redeploying...`); } - if (!sandboxInstanceId || redeploy) { - const results = await this.createNewPreview(); - if (!results || !results.runId || !results.previewURL) { - throw new Error('Failed to create new deployment'); - } - sandboxInstanceId = results.runId; - previewURL = results.previewURL; - tunnelURL = results.tunnelURL; - redeployed = true; - this.setState({ - ...this.state, - sandboxInstanceId, + const results = await this.createNewPreview(); + if (!results || !results.runId || !results.previewURL) { + throw new Error('Failed to create new deployment'); + } + + this.setState({ + ...this.state, + sandboxInstanceId: results.runId, + }); + + return { + sandboxInstanceId: results.runId, + previewURL: results.previewURL, + tunnelURL: results.tunnelURL, + redeployed: true + }; + } + + private async executeSetupCommands(sandboxInstanceId: string): Promise { + if (!this.state.commandsHistory || this.state.commandsHistory.length === 0) { + return; + } + + let cmds = this.state.commandsHistory; + if (cmds.length > SimpleCodeGeneratorAgent.MAX_COMMANDS_HISTORY) { + cmds = Array.from(new Set(this.state.commandsHistory)); + } + + this.broadcast(WebSocketMessageResponses.COMMAND_EXECUTING, { + message: "Executing setup commands", + commands: cmds, + }); + + try { + await this.withTimeout( + this.getSandboxServiceClient().executeCommands(sandboxInstanceId, cmds), + SimpleCodeGeneratorAgent.COMMAND_TIMEOUT_MS, + 'Command execution timed out' + ); + this.broadcast(WebSocketMessageResponses.COMMAND_EXECUTED, { + message: "Setup commands executed successfully", + commands: cmds, + output: "Setup commands executed successfully", + }); + } catch (error) { + this.logger().error('Failed to execute commands', error); + this.broadcast(WebSocketMessageResponses.COMMAND_EXECUTION_FAILED, { + message: "Failed to execute setup commands", + commands: cmds, + error: String(error), }); + } + } - if (this.state.commandsHistory && this.state.commandsHistory.length > 0) { - // Run all commands in background - let cmds = this.state.commandsHistory; - if (cmds.length > 10) { - cmds = Array.from(new Set(this.state.commandsHistory)); - // I am aware this will messup the ordering of commands and may cause issues but those would be in very rare cases - // because usually LLMs will only generate install commands or rm commands. - // This is to handle the bug still present in a lot of apps because of an exponential growth of commands - } - this.broadcast(WebSocketMessageResponses.COMMAND_EXECUTING, { - message: "Executing setup commands", - commands: cmds, - }); - try { - await Promise.race([ - this.getSandboxServiceClient().executeCommands(sandboxInstanceId, cmds), - new Promise((_, reject) => setTimeout(() => reject(new Error('Command execution timed out after 60 seconds')), 60000)) - ]); - this.broadcast(WebSocketMessageResponses.COMMAND_EXECUTED, { - message: "Setup commands executed successfully", - commands: cmds, - output: "Setup commands executed successfully", - }); - } catch (error) { - this.logger().error('Failed to execute commands', error); - this.broadcast(WebSocketMessageResponses.COMMAND_EXECUTION_FAILED, { - message: "Failed to execute setup commands", - commands: cmds, - error: String(error), - }); - } - } + private clearHealthCheckInterval(): void { + if (this.healthCheckInterval !== null) { + clearInterval(this.healthCheckInterval); + this.healthCheckInterval = null; + } + } - // Clear any existing health check interval before creating a new one - if (this.healthCheckInterval !== null) { - clearInterval(this.healthCheckInterval); - this.healthCheckInterval = null; + private startHealthCheckInterval(sandboxInstanceId: string): void { + this.clearHealthCheckInterval(); + + this.healthCheckInterval = setInterval(async () => { + if (this.currentDeploymentPromise !== null) { + return; } - - // Launch a set interval to check the health of the deployment. If it fails, redeploy - this.healthCheckInterval = setInterval(async () => { - // Don't trigger redeploy if there's already a deployment in progress - if (this.currentDeploymentPromise !== null) { - return; - } - - const status = await this.getSandboxServiceClient().getInstanceStatus(sandboxInstanceId!); + + try { + const status = await this.getSandboxServiceClient().getInstanceStatus(sandboxInstanceId); if (!status || !status.success || !status.isHealthy) { - this.logger().error(`DEPLOYMENT CHECK FAILED: Failed to get status for instance ${sandboxInstanceId}, redeploying...`); - // Clear the interval to prevent it from running again - if (this.healthCheckInterval !== null) { - clearInterval(this.healthCheckInterval); - this.healthCheckInterval = null; + this.healthCheckFailures++; + this.clearHealthCheckInterval(); + + if (this.healthCheckFailures >= SimpleCodeGeneratorAgent.MAX_HEALTH_CHECK_FAILURES) { + this.logger().error('Max health check failures reached, stopping health checks'); + this.broadcast(WebSocketMessageResponses.ERROR, { + error: 'Sandbox instance is unhealthy and cannot be recovered' + }); + return; } + + this.logger().error(`DEPLOYMENT CHECK FAILED: Failed to get status for instance ${sandboxInstanceId}, redeploying...`); await this.deployToSandbox([], true); + } else { + this.healthCheckFailures = 0; } - }, 5000); + } catch (error) { + this.logger().error('Health check error', error); + } + }, SimpleCodeGeneratorAgent.HEALTH_CHECK_INTERVAL_MS); + } - // Launch a static analysis on the codebase in the background to build cache - // this.runStaticAnalysisCode(); + private async ensurePreviewExists(redeploy: boolean = false) { + const { sandboxInstanceId, previewURL, tunnelURL, redeployed } = + await this.getOrCreateInstance(redeploy); + + if (redeployed) { + await this.executeSetupCommands(sandboxInstanceId); + this.startHealthCheckInterval(sandboxInstanceId); } - - return { - sandboxInstanceId, - previewURL, - tunnelURL, - redeployed, - }; + + return { sandboxInstanceId, previewURL, tunnelURL, redeployed }; } private async executeDeployment(files: FileOutputType[] = [], redeploy: boolean = false, commitMessage?: string, clearLogs: boolean = false, retries: number = MAX_DEPLOYMENT_RETRIES): Promise { @@ -2141,16 +2018,7 @@ export class SimpleCodeGeneratorAgent extends Agent { redeployed, } = await this.ensurePreviewExists(redeploy); - // Deploy files - const filesToWrite = files.length > 0 && !redeployed // If redeployed, we should write all files again - ? files.map(file => ({ - filePath: file.filePath, - fileContents: file.fileContents - })) - : Object.values(this.state.generatedFilesMap).map(file => ({ - filePath: file.filePath, - fileContents: file.fileContents - })); + const filesToWrite = this.getFilesToDeploy(files, redeployed); if (filesToWrite.length > 0) { const writeResponse = await this.getSandboxServiceClient().writeFiles(sandboxInstanceId, filesToWrite, commitMessage); diff --git a/worker/agents/core/stateMigration.ts b/worker/agents/core/stateMigration.ts new file mode 100644 index 00000000..9e60b6ba --- /dev/null +++ b/worker/agents/core/stateMigration.ts @@ -0,0 +1,177 @@ +import { CodeGenState, FileState } from './state'; +import { StructuredLogger } from '../../logger'; + +export class StateMigration { + static migrateIfNeeded(state: CodeGenState, logger: StructuredLogger): CodeGenState | null { + let needsMigration = false; + + const migrateFile = (file: any): any => { + const hasOldFormat = 'file_path' in file || 'file_contents' in file || 'file_purpose' in file; + + if (hasOldFormat) { + return { + filePath: file.filePath || file.file_path, + fileContents: file.fileContents || file.file_contents, + filePurpose: file.filePurpose || file.file_purpose, + }; + } + return file; + }; + + const migratedFilesMap: Record = {}; + for (const [key, file] of Object.entries(state.generatedFilesMap)) { + const migratedFile = migrateFile(file); + + migratedFilesMap[key] = { + ...migratedFile, + lasthash: migratedFile.lasthash || '', + lastmodified: migratedFile.lastmodified || Date.now(), + unmerged: migratedFile.unmerged || [] + }; + + if (migratedFile !== file) { + needsMigration = true; + } + } + + let migratedTemplateDetails = state.templateDetails; + if (migratedTemplateDetails?.files) { + const migratedTemplateFiles = migratedTemplateDetails.files.map(file => { + const migratedFile = migrateFile(file); + if (migratedFile !== file) { + needsMigration = true; + } + return migratedFile; + }); + + if (needsMigration) { + migratedTemplateDetails = { + ...migratedTemplateDetails, + files: migratedTemplateFiles + }; + } + } + + let migratedConversationMessages = state.conversationMessages; + const MIN_MESSAGES_FOR_CLEANUP = 25; + + if (migratedConversationMessages && migratedConversationMessages.length > 0) { + const originalCount = migratedConversationMessages.length; + + const seen = new Set(); + const uniqueMessages = []; + + for (const message of migratedConversationMessages) { + let key = message.conversationId; + if (!key) { + const contentStr = typeof message.content === 'string' + ? message.content.substring(0, 100) + : JSON.stringify(message.content || '').substring(0, 100); + key = `${message.role || 'unknown'}_${contentStr}_${Date.now()}`; + } + + if (!seen.has(key)) { + seen.add(key); + uniqueMessages.push(message); + } + } + + uniqueMessages.sort((a, b) => { + const getTimestamp = (msg: any) => { + if (msg.conversationId && typeof msg.conversationId === 'string' && msg.conversationId.startsWith('conv-')) { + const parts = msg.conversationId.split('-'); + if (parts.length >= 2) { + return parseInt(parts[1]) || 0; + } + } + return 0; + }; + return getTimestamp(a) - getTimestamp(b); + }); + + if (uniqueMessages.length > MIN_MESSAGES_FOR_CLEANUP) { + const realConversations = []; + const internalMemos = []; + + for (const message of uniqueMessages) { + const content = typeof message.content === 'string' ? message.content : JSON.stringify(message.content || ''); + const isInternalMemo = content.includes('****') || content.includes('Project Updates:'); + + if (isInternalMemo) { + internalMemos.push(message); + } else { + realConversations.push(message); + } + } + + logger.info('Conversation cleanup analysis', { + totalUniqueMessages: uniqueMessages.length, + realConversations: realConversations.length, + internalMemos: internalMemos.length, + willRemoveInternalMemos: uniqueMessages.length > MIN_MESSAGES_FOR_CLEANUP + }); + + migratedConversationMessages = realConversations; + } else { + migratedConversationMessages = uniqueMessages; + } + + if (migratedConversationMessages.length !== originalCount) { + logger.info('Fixed conversation message exponential bloat', { + originalCount, + deduplicatedCount: uniqueMessages.length, + finalCount: migratedConversationMessages.length, + duplicatesRemoved: originalCount - uniqueMessages.length, + internalMemosRemoved: uniqueMessages.length - migratedConversationMessages.length + }); + needsMigration = true; + } + } + + let migratedInferenceContext = state.inferenceContext; + if (migratedInferenceContext && 'userApiKeys' in migratedInferenceContext) { + migratedInferenceContext = { + ...migratedInferenceContext + }; + + delete (migratedInferenceContext as any).userApiKeys; + needsMigration = true; + } + + const stateHasDeprecatedProps = 'latestScreenshot' in (state as any); + if (stateHasDeprecatedProps) { + needsMigration = true; + } + + const stateHasProjectUpdatesAccumulator = 'projectUpdatesAccumulator' in (state as any); + if (!stateHasProjectUpdatesAccumulator) { + needsMigration = true; + } + + if (needsMigration) { + logger.info('Migrating state: schema format, conversation cleanup, and security fixes', { + generatedFilesCount: Object.keys(migratedFilesMap).length, + templateFilesCount: migratedTemplateDetails?.files?.length || 0, + finalConversationCount: migratedConversationMessages?.length || 0, + removedUserApiKeys: state.inferenceContext && 'userApiKeys' in state.inferenceContext + }); + + const newState = { + ...state, + generatedFilesMap: migratedFilesMap, + templateDetails: migratedTemplateDetails, + conversationMessages: migratedConversationMessages, + inferenceContext: migratedInferenceContext, + projectUpdatesAccumulator: [] + }; + + if (stateHasDeprecatedProps) { + delete (newState as any).latestScreenshot; + } + + return newState; + } + + return null; + } +} From b6844b64907a577245fb18af9ffd887bd2c3411b Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sun, 19 Oct 2025 01:57:48 -0400 Subject: [PATCH 038/150] refactor: deployment manager abstracted away, removed useless client errors --- worker/agents/core/simpleGeneratorAgent.ts | 813 +++--------------- worker/agents/core/state.ts | 3 +- worker/agents/core/types.ts | 3 +- worker/agents/core/websocket.ts | 4 - worker/agents/domain/values/IssueReport.ts | 21 +- worker/agents/prompts.ts | 14 +- worker/agents/schemas.ts | 21 - .../implementations/BaseAgentService.ts | 71 ++ .../implementations/CommandManager.ts | 94 ++ .../implementations/DeploymentManager.ts | 670 +++++++++++++++ .../services/interfaces/IAnalysisManager.ts | 0 .../services/interfaces/ICommandManager.ts | 26 + .../services/interfaces/IDeploymentManager.ts | 102 +++ .../services/interfaces/IServiceOptions.ts | 14 + worker/api/websocketTypes.ts | 15 +- worker/services/sandbox/BaseSandboxService.ts | 2 +- 16 files changed, 1121 insertions(+), 752 deletions(-) create mode 100644 worker/agents/services/implementations/BaseAgentService.ts create mode 100644 worker/agents/services/implementations/CommandManager.ts create mode 100644 worker/agents/services/implementations/DeploymentManager.ts create mode 100644 worker/agents/services/interfaces/IAnalysisManager.ts create mode 100644 worker/agents/services/interfaces/ICommandManager.ts create mode 100644 worker/agents/services/interfaces/IDeploymentManager.ts create mode 100644 worker/agents/services/interfaces/IServiceOptions.ts diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index d297f794..1910a064 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -11,13 +11,16 @@ import { GitHubPushRequest, PreviewType, StaticAnalysisResponse, TemplateDetails import { GitHubExportResult } from '../../services/github/types'; import { CodeGenState, CurrentDevState, MAX_PHASES } from './state'; import { AllIssues, AgentSummary, AgentInitArgs, PhaseExecutionResult, UserContext } from './types'; -import { MAX_DEPLOYMENT_RETRIES, PREVIEW_EXPIRED_ERROR, WebSocketMessageResponses } from '../constants'; +import { PREVIEW_EXPIRED_ERROR, WebSocketMessageResponses } from '../constants'; import { broadcastToConnections, handleWebSocketClose, handleWebSocketMessage } from './websocket'; import { createObjectLogger, StructuredLogger } from '../../logger'; import { ProjectSetupAssistant } from '../assistants/projectsetup'; import { UserConversationProcessor, RenderToolCall } from '../operations/UserConversationProcessor'; import { FileManager } from '../services/implementations/FileManager'; import { StateManager } from '../services/implementations/StateManager'; +import { CommandManager } from '../services/implementations/CommandManager'; +import { DeploymentManager } from '../services/implementations/DeploymentManager'; +import { ServiceOptions } from '../services/interfaces/IServiceOptions'; // import { WebSocketBroadcaster } from '../services/implementations/WebSocketBroadcaster'; import { GenerationContext } from '../domain/values/GenerationContext'; import { IssueReport } from '../domain/values/IssueReport'; @@ -35,46 +38,20 @@ import { AGENT_CONFIG } from '../inferutils/config'; import { ModelConfigService } from '../../database/services/ModelConfigService'; import { FileFetcher, fixProjectIssues } from '../../services/code-fixer'; import { FastCodeFixerOperation } from '../operations/PostPhaseCodeFixer'; -import { getProtocolForHost } from '../../utils/urls'; import { looksLikeCommand } from '../utils/common'; import { generateBlueprint } from '../planning/blueprint'; import { prepareCloudflareButton } from '../../utils/deployToCf'; import { AppService } from '../../database'; import { RateLimitExceededError } from 'shared/types/errors'; -import { generateId } from 'worker/utils/idGenerator'; import { ImageAttachment, type ProcessedImageAttachment } from '../../types/image-attachment'; import { OperationOptions } from '../operations/common'; import { CodingAgentInterface } from '../services/implementations/CodingAgent'; -import { generateAppProxyToken, generateAppProxyUrl } from 'worker/services/aigateway-proxy/controller'; import { ImageType, uploadImage } from 'worker/utils/images'; import { ConversationMessage, ConversationState } from '../inferutils/common'; import { DeepCodeDebugger } from '../assistants/codeDebugger'; import { DeepDebugResult } from './types'; import { StateMigration } from './stateMigration'; -interface WebhookPayload { - event: { - eventType: 'runtime_error'; - payload: { - error?: { message: string }; - runId?: string; - status?: string; - deploymentType?: string; - instanceInfo?: unknown; - command?: string; - }; - instanceId?: string; - runId?: string; - timestamp?: string; - }; - context: { - sessionId?: string; - agentId?: string; - userId?: string; - }; - source: string; -} - interface Operations { codeReview: CodeReviewOperation; regenerateFile: FileRegenerationOperation; @@ -97,30 +74,27 @@ const DEFAULT_CONVERSATION_SESSION_ID = 'default'; * - Deployment to sandbox service */ export class SimpleCodeGeneratorAgent extends Agent { - private static readonly DEPLOYMENT_TIMEOUT_MS = 60_000; - private static readonly COMMAND_TIMEOUT_MS = 60_000; - private static readonly HEALTH_CHECK_INTERVAL_MS = 5_000; - private static readonly MAX_COMMANDS_HISTORY = 50; + private static readonly MAX_COMMANDS_HISTORY = 10; private static readonly PROJECT_NAME_PREFIX_MAX_LENGTH = 20; - private static readonly MAX_HEALTH_CHECK_FAILURES = 3; protected projectSetupAssistant: ProjectSetupAssistant | undefined; protected sandboxServiceClient: BaseSandboxService | undefined; - protected fileManager: FileManager = new FileManager( - new StateManager(() => this.state, (s) => this.setState(s)), - ); + protected stateManager!: StateManager; + protected fileManager!: FileManager; protected codingAgent: CodingAgentInterface = new CodingAgentInterface(this); + + // Service layer for business logic + protected commandManager!: CommandManager; + protected deploymentManager!: DeploymentManager; private previewUrlCache: string = ''; // In-memory storage for user-uploaded images (not persisted in DO state) - // These are temporary and will be lost if the DO is evicted private pendingUserImages: ProcessedImageAttachment[] = [] private generationPromise: Promise | null = null; private deepDebugPromise: Promise<{ transcript: string } | { error: string }> | null = null; private currentAbortController?: AbortController; - private healthCheckFailures = 0; protected operations: Operations = { codeReview: new CodeReviewOperation(), @@ -132,11 +106,6 @@ export class SimpleCodeGeneratorAgent extends Agent { processUserMessage: new UserConversationProcessor() }; - // Deployment queue management to prevent concurrent deployments - private currentDeploymentPromise: Promise | null = null; - - private healthCheckInterval: ReturnType | null = null; - public _logger: StructuredLogger | undefined; private initLogger(agentId: string, sessionId: string, userId: string) { @@ -171,7 +140,6 @@ export class SimpleCodeGeneratorAgent extends Agent { templateDetails: {} as TemplateDetails, commandsHistory: [], lastPackageJson: '', - clientReportedErrors: [], pendingUserInputs: [], inferenceContext: {} as InferenceContext, sessionId: '', @@ -243,6 +211,33 @@ export class SimpleCodeGeneratorAgent extends Agent { super(ctx, env); this.sql`CREATE TABLE IF NOT EXISTS full_conversations (id TEXT PRIMARY KEY, messages TEXT)`; this.sql`CREATE TABLE IF NOT EXISTS compact_conversations (id TEXT PRIMARY KEY, messages TEXT)`; + + // Initialize StateManager + this.stateManager = new StateManager( + () => this.state, + (s) => this.setState(s) + ); + + // Initialize FileManager + this.fileManager = new FileManager(this.stateManager); + + // Initialize service layer + const serviceOptions: ServiceOptions = { + stateManager: this.stateManager, + fileManager: this.fileManager, + getSandboxClient: () => this.getSandboxServiceClient(), + getLogger: () => this.logger() + }; + + this.commandManager = new CommandManager( + serviceOptions, + SimpleCodeGeneratorAgent.MAX_COMMANDS_HISTORY + ); + this.deploymentManager = new DeploymentManager( + serviceOptions, + this.env, + SimpleCodeGeneratorAgent.PROJECT_NAME_PREFIX_MAX_LENGTH + ); } async saveToDatabase() { @@ -356,9 +351,10 @@ export class SimpleCodeGeneratorAgent extends Agent { try { super.setState(state); } catch (error) { - this.logger().error("Error setting state:", error); - this.broadcast(WebSocketMessageResponses.ERROR, { - error: `Error setting state: ${error instanceof Error ? error.message : String(error)}; Original state: ${JSON.stringify(this.state, null, 2)}; New state: ${JSON.stringify(state, null, 2)}` + this.broadcastError("Error setting state", error); + this.logger().error("State details:", { + originalState: JSON.stringify(this.state, null, 2), + newState: JSON.stringify(state, null, 2) }); } } @@ -382,24 +378,8 @@ export class SimpleCodeGeneratorAgent extends Agent { } getSessionId() { - return this.state.sessionId - } - - resetSessionId() { - const newSessionId = generateId(); - this.logger().info(`New Sandbox sessionId initialized: ${newSessionId}. Old sessionId: ${this.state.sessionId}`) - this.setState({ - ...this.state, - sessionId: newSessionId - }) - // Reset sandbox service client - this.sandboxServiceClient = undefined; - - // Clear health check interval since we're abandoning the old instance - if (this.healthCheckInterval !== null) { - clearInterval(this.healthCheckInterval); - this.healthCheckInterval = null; - } + // Delegate to deploymentManager which now manages sessionId + return this.deploymentManager.getSessionId(); } getSandboxServiceClient(): BaseSandboxService { @@ -496,23 +476,6 @@ export class SimpleCodeGeneratorAgent extends Agent { }; } - private async withTimeout( - operation: Promise, - timeoutMs: number, - errorMsg: string, - onTimeout?: () => void - ): Promise { - return Promise.race([ - operation, - new Promise((_, reject) => - setTimeout(() => { - onTimeout?.(); - reject(new Error(errorMsg)); - }, timeoutMs) - ) - ]); - } - private broadcastError(context: string, error: unknown): void { const errorMessage = error instanceof Error ? error.message : String(error); this.logger().error(`${context}:`, error); @@ -521,20 +484,6 @@ export class SimpleCodeGeneratorAgent extends Agent { }); } - private getFilesToDeploy( - requestedFiles: FileOutputType[], - redeployed: boolean - ): Array<{ filePath: string; fileContents: string }> { - if (!requestedFiles || requestedFiles.length === 0 || redeployed) { - requestedFiles = Object.values(this.state.generatedFilesMap) - } - - return requestedFiles.map(file => ({ - filePath: file.filePath, - fileContents: file.fileContents - })); - } - async generateReadme() { this.logger().info('Generating README.md'); // Only generate if it doesn't exist @@ -665,14 +614,12 @@ export class SimpleCodeGeneratorAgent extends Agent { this.logger().info("State machine completed successfully"); } catch (error) { - this.logger().error("Error in state machine:", error); if (error instanceof RateLimitExceededError) { + this.logger().error("Error in state machine:", error); this.broadcast(WebSocketMessageResponses.RATE_LIMIT_ERROR, { error }); + } else { + this.broadcastError("Error during generation", error); } - const errorMessage = error instanceof Error ? error.message : String(error); - this.broadcast(WebSocketMessageResponses.ERROR, { - error: `Error during generation: ${errorMessage}` - }); } finally { // Clear abort controller after generation completes this.clearAbortController(); @@ -748,14 +695,10 @@ export class SimpleCodeGeneratorAgent extends Agent { userContext: userContext, }; } catch (error) { - this.logger().error("Error generating phase", error); if (error instanceof RateLimitExceededError) { throw error; } - this.broadcast(WebSocketMessageResponses.ERROR, { - message: "Error generating phase", - error: error - }); + this.broadcastError("Error generating phase", error); return { currentDevState: CurrentDevState.IDLE, }; @@ -794,7 +737,6 @@ export class SimpleCodeGeneratorAgent extends Agent { currentIssues = { runtimeErrors: await this.fetchRuntimeErrors(true), staticAnalysis: staticAnalysis, - clientErrors: this.state.clientReportedErrors }; } else { currentIssues = await this.fetchAllIssues(true) @@ -803,7 +745,6 @@ export class SimpleCodeGeneratorAgent extends Agent { currentIssues = { runtimeErrors: [], staticAnalysis: { success: true, lint: { issues: [] }, typecheck: { issues: [] } }, - clientErrors: [] } } // Implement the phase with user context (suggestions and images) @@ -1289,7 +1230,6 @@ export class SimpleCodeGeneratorAgent extends Agent { this.broadcast(WebSocketMessageResponses.CODE_REVIEWING, { message: "Running code review...", staticAnalysis: issues.staticAnalysis, - clientErrors: issues.clientErrors, runtimeErrors: issues.runtimeErrors }); @@ -1375,39 +1315,24 @@ export class SimpleCodeGeneratorAgent extends Agent { } async fetchRuntimeErrors(clear: boolean = true) { - await this.waitForPreview(); - - if (!this.state.sandboxInstanceId || !this.fileManager) { - this.logger().warn("No sandbox instance ID available to fetch errors from."); - return []; - } + await this.deploymentManager.waitForPreview(); try { - const resp = await this.getSandboxServiceClient().getInstanceErrors(this.state.sandboxInstanceId); - if (!resp || !resp.success) { - this.logger().error(`Failed to fetch runtime errors: ${resp?.error || 'Unknown error'}, Will initiate redeploy`); - this.deployToSandbox(); - return []; - } - - const errors = resp?.errors || []; + const errors = await this.deploymentManager.fetchRuntimeErrors(clear); if (errors.length > 0) { - this.logger().info(`Found ${errors.length} runtime errors: ${errors.map(e => e.message).join(', ')}`); this.broadcast(WebSocketMessageResponses.RUNTIME_ERROR_FOUND, { errors, message: "Runtime errors found", count: errors.length }); - - if (clear) { - await this.getSandboxServiceClient().clearInstanceErrors(this.state.sandboxInstanceId); - } } return errors; } catch (error) { this.logger().error("Exception fetching runtime errors:", error); + // If fetch fails, initiate redeploy + this.deployToSandbox(); return []; } } @@ -1417,55 +1342,18 @@ export class SimpleCodeGeneratorAgent extends Agent { * This helps catch potential issues early in the development process */ async runStaticAnalysisCode(files?: string[]): Promise { - const { sandboxInstanceId } = this.state; - - if (!sandboxInstanceId) { - this.logger().warn("No sandbox instance ID available to lint code."); - return { success: false, lint: { issues: [], }, typecheck: { issues: [], } }; - } - - this.logger().info(`Linting code in sandbox instance ${sandboxInstanceId}`); - - const targetFiles = Array.isArray(files) && files.length > 0 - ? files - : this.fileManager.getGeneratedFilePaths(); - try { - const analysisResponse = await this.getSandboxServiceClient()?.runStaticAnalysisCode(sandboxInstanceId, targetFiles); - - if (!analysisResponse || analysisResponse.error) { - const errorMsg = `Code linting failed: ${analysisResponse?.error || 'Unknown error'}, full response: ${JSON.stringify(analysisResponse)}`; - this.logger().error(errorMsg); - this.broadcast(WebSocketMessageResponses.ERROR, { error: errorMsg, analysisResponse }); - throw new Error(errorMsg); - } + const analysisResponse = await this.deploymentManager.runStaticAnalysis(files); const { lint, typecheck } = analysisResponse; - const { issues: lintIssues, summary: lintSummary } = lint; - - this.logger().info(`Linting found ${lintIssues.length} issues: ` + - `${lintSummary?.errorCount || 0} errors, ` + - `${lintSummary?.warningCount || 0} warnings, ` + - `${lintSummary?.infoCount || 0} info`); - - const { issues: typeCheckIssues, summary: typeCheckSummary } = typecheck; - - this.logger().info(`Typecheck found ${typeCheckIssues.length} issues: ` + - `${typeCheckSummary?.errorCount || 0} errors, ` + - `${typeCheckSummary?.warningCount || 0} warnings, ` + - `${typeCheckSummary?.infoCount || 0} info`); - this.broadcast(WebSocketMessageResponses.STATIC_ANALYSIS_RESULTS, { - lint: { issues: lintIssues, summary: lintSummary }, - typecheck: { issues: typeCheckIssues, summary: typeCheckSummary } + lint: { issues: lint.issues, summary: lint.summary }, + typecheck: { issues: typecheck.issues, summary: typecheck.summary } }); return analysisResponse; } catch (error) { - this.logger().error("Error linting code:", error); - const errorMessage = error instanceof Error ? error.message : String(error); - this.broadcast(WebSocketMessageResponses.ERROR, { error: `Failed to lint code: ${errorMessage}` }); - // throw new Error(`Failed to lint code: ${errorMessage}`); + this.broadcastError("Failed to lint code", error); return { success: false, lint: { issues: [], }, typecheck: { issues: [], } }; } } @@ -1496,9 +1384,7 @@ export class SimpleCodeGeneratorAgent extends Agent { } this.logger().info(`Fast smart code fixes applied in ${Date.now() - startTime}ms`); } catch (error) { - this.logger().error("Error applying fast smart code fixes:", error); - const errorMessage = error instanceof Error ? error.message : String(error); - this.broadcast(WebSocketMessageResponses.ERROR, { error: `Failed to apply fast smart code fixes: ${errorMessage}` }); + this.broadcastError("Failed to apply fast smart code fixes", error); return; } } @@ -1594,10 +1480,7 @@ export class SimpleCodeGeneratorAgent extends Agent { } this.logger().info(`Applied deterministic code fixes: ${JSON.stringify(fixResult, null, 2)}`); } catch (error) { - this.logger().error('Error applying deterministic code fixes:', error); - this.broadcast(WebSocketMessageResponses.ERROR, { - error: `Deterministic code fixer failed: ${error instanceof Error ? error.message : String(error)}` - }); + this.broadcastError('Deterministic code fixer failed', error); } // return undefined; } @@ -1607,11 +1490,9 @@ export class SimpleCodeGeneratorAgent extends Agent { this.fetchRuntimeErrors(resetIssues), this.runStaticAnalysisCode() ]); + this.logger().info("Fetched all issues:", JSON.stringify({ runtimeErrors, staticAnalysis })); - const clientErrors = this.state.clientReportedErrors; - this.logger().info("Fetched all issues:", JSON.stringify({ runtimeErrors, staticAnalysis, clientErrors })); - - return { runtimeErrors, staticAnalysis, clientErrors }; + return { runtimeErrors, staticAnalysis }; } async updateProjectName(newName: string): Promise { @@ -1770,7 +1651,6 @@ export class SimpleCodeGeneratorAgent extends Agent { lint: { issues: [] }, typecheck: { issues: [] } }, - clientErrors: [] }, { suggestions: requirements }, true, // streamChunks @@ -1800,315 +1680,40 @@ export class SimpleCodeGeneratorAgent extends Agent { } async deployToSandbox(files: FileOutputType[] = [], redeploy: boolean = false, commitMessage?: string, clearLogs: boolean = false): Promise { - // If there's already a deployment in progress, wait for it to complete - if (this.currentDeploymentPromise) { - this.logger().info('Deployment already in progress, waiting for completion'); - try { - const result = await this.currentDeploymentPromise; - if (result) { - this.logger().info('Previous deployment completed successfully, returning its result', { result }); - return result; - } - } catch (error) { - // Only proceed with new deployment if previous one failed - this.logger().warn('Previous deployment failed, proceeding with new deployment:', error); - } - return null; - } - - this.logger().info("Deploying to sandbox", { files, redeploy, commitMessage, sessionId: this.state.sessionId }); - - this.currentDeploymentPromise = this.executeDeployment(files, redeploy, commitMessage, clearLogs); - - try { - const result = await this.withTimeout( - this.currentDeploymentPromise, - SimpleCodeGeneratorAgent.DEPLOYMENT_TIMEOUT_MS, - 'Deployment timed out', - () => { - this.logger().warn('Deployment timed out, resetting sessionId to provision new sandbox instance'); - this.resetSessionId(); - } - ); - return result; - } finally { - this.currentDeploymentPromise = null; - } - } - - private async createNewPreview(): Promise { - // Create new deployment - const templateName = this.state.templateDetails?.name || 'scratch'; - // Generate a unique suffix - let prefix = (this.state.blueprint?.projectName || templateName).toLowerCase().replace(/[^a-z0-9]/g, '-'); - const uniqueSuffix = generateId(); - prefix = prefix.slice(0, SimpleCodeGeneratorAgent.PROJECT_NAME_PREFIX_MAX_LENGTH); - const projectName = `${prefix}-${uniqueSuffix}`.toLowerCase(); - - // Generate webhook URL for this agent instance - const webhookUrl = this.generateWebhookUrl(); - - // If AI template is configured, pass AI vars - let localEnvVars: Record = {}; - if (this.state.templateDetails.name.includes('agents')) { - localEnvVars = { - "CF_AI_BASE_URL": generateAppProxyUrl(this.env), - "CF_AI_API_KEY": await generateAppProxyToken(this.state.inferenceContext.agentId, this.state.inferenceContext.userId, this.env) - } - } - - const createResponse = await this.getSandboxServiceClient().createInstance(templateName, `v1-${projectName}`, webhookUrl, localEnvVars); - if (!createResponse || !createResponse.success || !createResponse.runId) { - throw new Error(`Failed to create sandbox instance: ${createResponse?.error || 'Unknown error'}`); - } - - this.logger().info(`Received createInstance response: ${JSON.stringify(createResponse, null, 2)}`) - - if (createResponse.runId && createResponse.previewURL) { - this.previewUrlCache = createResponse.previewURL; - return createResponse; - } - - throw new Error(`Failed to create sandbox instance: ${createResponse?.error || 'Unknown error'}`); - } - - private async getOrCreateInstance(redeploy: boolean): Promise<{ - sandboxInstanceId: string; - previewURL: string | undefined; - tunnelURL: string | undefined; - redeployed: boolean; - }> { - const { sandboxInstanceId } = this.state; - - if (sandboxInstanceId && !redeploy) { - const status = await this.getSandboxServiceClient().getInstanceStatus(sandboxInstanceId); - if (status.success && status.isHealthy) { - this.logger().info(`DEPLOYMENT CHECK PASSED: Instance ${sandboxInstanceId} is running`); - return { - sandboxInstanceId, - previewURL: status.previewURL, - tunnelURL: status.tunnelURL, - redeployed: false - }; - } - this.logger().error(`DEPLOYMENT CHECK FAILED: Failed to get status for instance ${sandboxInstanceId}, redeploying...`); - } - - const results = await this.createNewPreview(); - if (!results || !results.runId || !results.previewURL) { - throw new Error('Failed to create new deployment'); - } - - this.setState({ - ...this.state, - sandboxInstanceId: results.runId, - }); - - return { - sandboxInstanceId: results.runId, - previewURL: results.previewURL, - tunnelURL: results.tunnelURL, - redeployed: true - }; - } - - private async executeSetupCommands(sandboxInstanceId: string): Promise { - if (!this.state.commandsHistory || this.state.commandsHistory.length === 0) { - return; - } - - let cmds = this.state.commandsHistory; - if (cmds.length > SimpleCodeGeneratorAgent.MAX_COMMANDS_HISTORY) { - cmds = Array.from(new Set(this.state.commandsHistory)); - } - - this.broadcast(WebSocketMessageResponses.COMMAND_EXECUTING, { - message: "Executing setup commands", - commands: cmds, - }); - - try { - await this.withTimeout( - this.getSandboxServiceClient().executeCommands(sandboxInstanceId, cmds), - SimpleCodeGeneratorAgent.COMMAND_TIMEOUT_MS, - 'Command execution timed out' - ); - this.broadcast(WebSocketMessageResponses.COMMAND_EXECUTED, { - message: "Setup commands executed successfully", - commands: cmds, - output: "Setup commands executed successfully", - }); - } catch (error) { - this.logger().error('Failed to execute commands', error); - this.broadcast(WebSocketMessageResponses.COMMAND_EXECUTION_FAILED, { - message: "Failed to execute setup commands", - commands: cmds, - error: String(error), - }); - } - } - - private clearHealthCheckInterval(): void { - if (this.healthCheckInterval !== null) { - clearInterval(this.healthCheckInterval); - this.healthCheckInterval = null; - } - } - - private startHealthCheckInterval(sandboxInstanceId: string): void { - this.clearHealthCheckInterval(); - - this.healthCheckInterval = setInterval(async () => { - if (this.currentDeploymentPromise !== null) { - return; - } - - try { - const status = await this.getSandboxServiceClient().getInstanceStatus(sandboxInstanceId); - if (!status || !status.success || !status.isHealthy) { - this.healthCheckFailures++; - this.clearHealthCheckInterval(); - - if (this.healthCheckFailures >= SimpleCodeGeneratorAgent.MAX_HEALTH_CHECK_FAILURES) { - this.logger().error('Max health check failures reached, stopping health checks'); - this.broadcast(WebSocketMessageResponses.ERROR, { - error: 'Sandbox instance is unhealthy and cannot be recovered' - }); - return; - } - - this.logger().error(`DEPLOYMENT CHECK FAILED: Failed to get status for instance ${sandboxInstanceId}, redeploying...`); - await this.deployToSandbox([], true); - } else { - this.healthCheckFailures = 0; + // Call deployment manager with callbacks for broadcasting at the right times + const result = await this.deploymentManager.deployToSandbox( + files, + redeploy, + commitMessage, + clearLogs, + { + onStarted: (data) => { + this.broadcast(WebSocketMessageResponses.DEPLOYMENT_STARTED, data); + }, + onCompleted: (data) => { + this.broadcast(WebSocketMessageResponses.DEPLOYMENT_COMPLETED, data); + }, + onError: (data) => { + this.broadcast(WebSocketMessageResponses.DEPLOYMENT_FAILED, data); } - } catch (error) { - this.logger().error('Health check error', error); } - }, SimpleCodeGeneratorAgent.HEALTH_CHECK_INTERVAL_MS); - } + ); - private async ensurePreviewExists(redeploy: boolean = false) { - const { sandboxInstanceId, previewURL, tunnelURL, redeployed } = - await this.getOrCreateInstance(redeploy); - - if (redeployed) { - await this.executeSetupCommands(sandboxInstanceId); - this.startHealthCheckInterval(sandboxInstanceId); - } - - return { sandboxInstanceId, previewURL, tunnelURL, redeployed }; + return result; } - - private async executeDeployment(files: FileOutputType[] = [], redeploy: boolean = false, commitMessage?: string, clearLogs: boolean = false, retries: number = MAX_DEPLOYMENT_RETRIES): Promise { - try { - this.broadcast(WebSocketMessageResponses.DEPLOYMENT_STARTED, { - message: "Deploying code to sandbox service", - files: files.map(file => ({ - filePath: file.filePath, - })) - }); - this.logger().info("Deploying code to sandbox service"); - - const { - sandboxInstanceId, - previewURL, - tunnelURL, - redeployed, - } = await this.ensurePreviewExists(redeploy); - - const filesToWrite = this.getFilesToDeploy(files, redeployed); - - if (filesToWrite.length > 0) { - const writeResponse = await this.getSandboxServiceClient().writeFiles(sandboxInstanceId, filesToWrite, commitMessage); - if (!writeResponse || !writeResponse.success) { - this.logger().error(`File writing failed. Error: ${writeResponse?.error}`); - throw new Error(`File writing failed. Error: ${writeResponse?.error}`); - } - } - if (clearLogs) { - try { - this.logger().info('Clearing logs and runtime errors for instance', { instanceId: sandboxInstanceId }); - await Promise.all([ - this.getSandboxServiceClient().getLogs(sandboxInstanceId, true), - this.getSandboxServiceClient().clearInstanceErrors(sandboxInstanceId) - ]); - } catch (error) { - this.logger().error('Failed to clear logs and runtime errors', error); - } - } - - - const preview = { - runId: sandboxInstanceId, - previewURL: previewURL, - tunnelURL: tunnelURL, - }; - - this.broadcast(WebSocketMessageResponses.DEPLOYMENT_COMPLETED, { - message: "Deployment completed", - ...preview, - }); - - return preview; - } catch (error) { - this.logger().error("Error deploying to sandbox service:", error, { sessionId: this.state.sessionId, sandboxInstanceId: this.state.sandboxInstanceId }); - const errorMsg = error instanceof Error ? error.message : String(error); - if (errorMsg.includes('Network connection lost') || errorMsg.includes('Container service disconnected') || errorMsg.includes('Internal error in Durable Object storage')) { - // For this particular error, reset the sandbox sessionId - this.resetSessionId(); - } - - this.setState({ - ...this.state, - sandboxInstanceId: undefined, - }); - if (retries > 0) { - this.broadcast(WebSocketMessageResponses.DEPLOYMENT_FAILED, { - error: `Error deploying to sandbox service: ${errorMsg}, Will retry...`, - }); - // Wait for exponential backoff - await new Promise(resolve => setTimeout(resolve, Math.pow(2, MAX_DEPLOYMENT_RETRIES - retries) * 1000)); - return this.executeDeployment(files, redeploy, commitMessage, clearLogs, retries - 1); - } - this.broadcast(WebSocketMessageResponses.DEPLOYMENT_FAILED, { - error: `Error deploying to sandbox service: ${errorMsg}. Please report an issue if this persists`, - }); - return null; - } - } - /** * Deploy the generated code to Cloudflare Workers */ async deployToCloudflare(): Promise<{ deploymentUrl?: string; workersUrl?: string } | null> { try { - this.logger().info('Starting Cloudflare deployment'); - await this.waitForPreview(); - this.broadcast(WebSocketMessageResponses.CLOUDFLARE_DEPLOYMENT_STARTED, { - message: 'Starting deployment to Cloudflare Workers...', - instanceId: this.state.sandboxInstanceId, - }); - - // Check if we have generated files - if (!this.state.generatedFilesMap || Object.keys(this.state.generatedFilesMap).length === 0) { - this.logger().error('No generated files available for deployment'); - this.broadcast(WebSocketMessageResponses.CLOUDFLARE_DEPLOYMENT_ERROR, { - message: 'Deployment failed: No generated code available', - error: 'No files have been generated yet' - }); - return null; - } - - // Check if we have a sandbox instance ID + // Ensure sandbox instance exists first if (!this.state.sandboxInstanceId) { - this.logger().info('[DeployToCloudflare] No sandbox instance ID available, will initiate deployment'); - // Need to redeploy + this.logger().info('No sandbox instance, deploying to sandbox first'); await this.deployToSandbox(); - + if (!this.state.sandboxInstanceId) { - this.logger().error('[DeployToCloudflare] Failed to deploy to sandbox service'); + this.logger().error('Failed to deploy to sandbox service'); this.broadcast(WebSocketMessageResponses.CLOUDFLARE_DEPLOYMENT_ERROR, { message: 'Deployment failed: Failed to deploy to sandbox service', error: 'Sandbox service unavailable' @@ -2117,71 +1722,43 @@ export class SimpleCodeGeneratorAgent extends Agent { } } - this.logger().info('[DeployToCloudflare] Prerequisites met, initiating deployment', { - sandboxInstanceId: this.state.sandboxInstanceId, - fileCount: Object.keys(this.state.generatedFilesMap).length - }); - - const deploymentResult = await this.getSandboxServiceClient().deployToCloudflareWorkers(this.state.sandboxInstanceId); - this.logger().info('[DeployToCloudflare] Deployment result:', deploymentResult); - if (!deploymentResult.success) { - this.logger().error('Deployment failed', { - message: deploymentResult.message, - error: deploymentResult.error - }); - if (deploymentResult.error?.includes('Failed to read instance metadata') || deploymentResult.error?.includes(`/bin/sh: 1: cd: can't cd to i-`)) { - this.logger().error('Deployment sandbox died'); - // Re-deploy + // Call service - handles orchestration, callbacks for broadcasting + const result = await this.deploymentManager.deployToCloudflare({ + onStarted: (data) => { + this.broadcast(WebSocketMessageResponses.CLOUDFLARE_DEPLOYMENT_STARTED, data); + }, + onCompleted: (data) => { + this.broadcast(WebSocketMessageResponses.CLOUDFLARE_DEPLOYMENT_COMPLETED, data); + }, + onError: (data) => { + this.broadcast(WebSocketMessageResponses.CLOUDFLARE_DEPLOYMENT_ERROR, data); + }, + onPreviewExpired: () => { + // Re-deploy sandbox and broadcast error this.deployToSandbox(); this.broadcast(WebSocketMessageResponses.CLOUDFLARE_DEPLOYMENT_ERROR, { message: PREVIEW_EXPIRED_ERROR, error: PREVIEW_EXPIRED_ERROR }); - } else { - this.broadcast(WebSocketMessageResponses.CLOUDFLARE_DEPLOYMENT_ERROR, { - message: `Deployment failed: ${deploymentResult.message}`, - error: deploymentResult.error || 'Unknown deployment error' - }); } - return null; - } - - const deploymentUrl = deploymentResult.deployedUrl; - - this.logger().info('[DeployToCloudflare] Cloudflare deployment completed successfully', { - deploymentUrl, - deploymentId: deploymentResult.deploymentId, - sandboxInstanceId: this.state.sandboxInstanceId, - message: deploymentResult.message }); - const appService = new AppService(this.env); - // Update cloudflare URL in database - await appService.updateDeploymentId( - this.getAgentId(), - deploymentResult.deploymentId || '' - ); - - // Broadcast success message - this.broadcast(WebSocketMessageResponses.CLOUDFLARE_DEPLOYMENT_COMPLETED, { - message: deploymentResult.message || 'Successfully deployed to Cloudflare Workers!', - deploymentUrl, - }); + // Update database with deployment ID if successful + if (result.deploymentUrl && result.deploymentId) { + const appService = new AppService(this.env); + await appService.updateDeploymentId( + this.getAgentId(), + result.deploymentId + ); + } - return { deploymentUrl }; + return result.deploymentUrl ? { deploymentUrl: result.deploymentUrl } : null; } catch (error) { - // return ErrorHandler.handleOperationError( - // this.logger(), - // this, - // 'Cloudflare deployment', - // error, - // WebSocketMessageResponses.CLOUDFLARE_DEPLOYMENT_ERROR - // ); - this.logger().error('Cloudflare deployment failed', error); + this.logger().error('Cloudflare deployment error:', error); this.broadcast(WebSocketMessageResponses.CLOUDFLARE_DEPLOYMENT_ERROR, { - message: `Deployment failed: ${error instanceof Error ? error.message : 'Unknown error'}`, - error: error instanceof Error ? error.message : 'Unknown error' + message: 'Deployment failed', + error: error instanceof Error ? error.message : String(error) }); return null; } @@ -2250,111 +1827,6 @@ export class SimpleCodeGeneratorAgent extends Agent { broadcastToConnections(this, msg, data || {} as WebSocketMessageData); } - /** - * Handle HTTP requests to this agent instance - * Includes webhook processing for internal requests - */ - async fetch(request: Request): Promise { - const url = new URL(request.url); - const pathname = url.pathname; - - // Handle internal webhook requests - if (pathname.startsWith('/webhook/')) { - return this.handleWebhook(request); - } - - // Delegate to parent class for other requests - return super.fetch(request); - } - - /** - * Generate webhook URL for this agent instance - */ - private generateWebhookUrl(): string { - // Use the agent's session ID as the agent identifier - const agentId = this.getAgentId() || 'unknown'; - - // Generate webhook URL with agent ID for routing - return `${getProtocolForHost(this.state.hostname)}://${this.state.hostname}/api/webhook/sandbox/${agentId}/runtime_error`; - } - - /** - * Handle webhook events from sandbox service - */ - async handleWebhook(request: Request): Promise { - try { - const url = new URL(request.url); - const pathParts = url.pathname.split('/'); - const eventType = pathParts[pathParts.length - 1]; - - this.logger().info('Received webhook from sandbox service', { - eventType, - agentId: this.getAgentId() - }); - - const payload = await request.json() as WebhookPayload; - const { event, context, source } = payload; - - if (source !== 'webhook') { - return new Response('Invalid source', { status: 400 }); - } - - // Process the webhook event - await this.processWebhookEvent(event, context); - - return new Response(JSON.stringify({ success: true }), { - headers: { 'Content-Type': 'application/json' }, - status: 200 - }); - - } catch (error) { - this.logger().error('Error handling webhook', error); - return new Response('Internal server error', { status: 500 }); - } - } - - /** - * Process webhook events and trigger appropriate actions - */ - private async processWebhookEvent(event: WebhookPayload['event'], context: WebhookPayload['context']): Promise { - try { - switch (event.eventType) { - case 'runtime_error': - await this.handleRuntimeErrorWebhook(event, context); - break; - default: - this.logger().warn('Unhandled webhook event type', { eventType: event.eventType }); - } - } catch (error) { - this.logger().error('Error processing webhook event', error); - } - } - - /** - * Handle runtime error webhook events - */ - private async handleRuntimeErrorWebhook(event: WebhookPayload['event'], _context: WebhookPayload['context']): Promise { - if (!event.payload.error) { - this.logger().error('Invalid runtime error event: No error provided'); - return; - } - this.logger().info('Processing runtime error webhook', { - errorMessage: event.payload.error.message, - runId: event.payload.runId, - instanceId: event.instanceId - }); - - // Broadcast runtime error to connected clients - this.broadcast(WebSocketMessageResponses.RUNTIME_ERROR_FOUND, { - error: event.payload.error, - runId: event.payload.runId, - instanceInfo: event.payload.instanceInfo, - instanceId: event.instanceId, - timestamp: event.timestamp, - source: 'webhook' - }); - } - /** * Execute commands with retry logic * Chunks commands and retries failed ones with AI assistance @@ -2481,22 +1953,13 @@ export class SimpleCodeGeneratorAgent extends Agent { const failedCommands = commands.filter(cmd => !successfulCommands.includes(cmd)); if (failedCommands.length > 0) { - this.logger().warn(`Failed to execute commands: ${failedCommands.join(", ")}`); - this.broadcast(WebSocketMessageResponses.ERROR, { - error: `Failed to execute commands: ${failedCommands.join(", ")}` - }); + this.broadcastError('Failed to execute commands', new Error(failedCommands.join(", "))); } else { this.logger().info(`All commands executed successfully: ${successfulCommands.join(", ")}`); } - // Add commands to history - this.setState({ - ...this.state, - commandsHistory: [ - ...(this.state.commandsHistory || []), - ...successfulCommands - ] - }); + // Add commands to history via service + this.commandManager.addToHistory(successfulCommands); } async getLogs(_reset?: boolean, durationSeconds?: number): Promise { @@ -2536,13 +1999,20 @@ export class SimpleCodeGeneratorAgent extends Agent { fileCount: Object.keys(this.state.generatedFilesMap).length }); - // Check if we have generated files - if (!this.state.generatedFilesMap || Object.keys(this.state.generatedFilesMap).length === 0) { - throw new Error('No generated files available for export'); + // Prepare README with Cloudflare button BEFORE push (if it exists) + const readmeFile = this.fileManager.getFile('README.md'); + if (readmeFile && readmeFile.fileContents.includes('[cloudflarebutton]')) { + readmeFile.fileContents = readmeFile.fileContents.replaceAll( + '[cloudflarebutton]', + prepareCloudflareButton(options.repositoryHtmlUrl, 'markdown') + ); + this.fileManager.saveGeneratedFile(readmeFile); + this.logger().info('README prepared with Cloudflare deploy button'); + + // Deploy updated README to sandbox so it's visible in preview + await this.deployToSandbox([readmeFile], false, "feat: README updated with Cloudflare deploy button"); } - await this.waitForPreview(); - // Broadcast export started this.broadcast(WebSocketMessageResponses.GITHUB_EXPORT_STARTED, { message: `Starting GitHub export to repository "${options.cloneUrl}"`, @@ -2550,43 +2020,26 @@ export class SimpleCodeGeneratorAgent extends Agent { isPrivate: options.isPrivate }); - - // Update progress for creating repository + // Update progress for uploading this.broadcast(WebSocketMessageResponses.GITHUB_EXPORT_PROGRESS, { message: 'Uploading to GitHub repository...', step: 'uploading_files', progress: 30 }); - const allFiles = this.fileManager.getGeneratedFiles(); - // Use consolidated export method that handles the complete flow - const exportResult = await this.getSandboxServiceClient().pushToGitHub(this.state.sandboxInstanceId!, options, allFiles); + // Call service to handle GitHub push (all files including prepared README) + const exportResult = await this.deploymentManager.pushToGitHub(options); if (!exportResult?.success) { throw new Error(`Failed to export to GitHub repository: ${exportResult?.error}`); } - this.logger().info('GitHub export completed successfully', { options, commitSha: exportResult.commitSha }); - - // Commit the readme - // First prepare the readme by replacing [cloudflarebutton] placeholder with actual thing - const readmeFile = this.fileManager.getFile('README.md'); - if (readmeFile) { - try { - readmeFile.fileContents = readmeFile.fileContents.replaceAll('[cloudflarebutton]', prepareCloudflareButton(options.repositoryHtmlUrl, 'markdown')); - this.fileManager.saveGeneratedFile(readmeFile); - await this.deployToSandbox([readmeFile], false, "feat: README updated with cloudflare deploy button"); - // Export again - await this.getSandboxServiceClient().pushToGitHub(this.state.sandboxInstanceId!, options, allFiles); - this.logger().info('Readme committed successfully'); - } catch (error) { - this.logger().error('Failed to commit readme', error); - } - } else { - this.logger().info('Readme not found, skipping commit'); - } + this.logger().info('GitHub export completed successfully', { + repositoryUrl: exportResult.repositoryUrl, + cloneUrl: exportResult.cloneUrl + }); - // Step 3: Finalize + // Finalize and update database this.broadcast(WebSocketMessageResponses.GITHUB_EXPORT_PROGRESS, { message: 'Finalizing GitHub export...', step: 'finalizing', @@ -2595,7 +2048,6 @@ export class SimpleCodeGeneratorAgent extends Agent { this.logger().info('Finalizing GitHub export...'); const appService = new AppService(this.env); - // Update database with GitHub repository URL and visibility await appService.updateGitHubRepository( this.getAgentId() || '', options.repositoryHtmlUrl || '', @@ -2690,17 +2142,14 @@ export class SimpleCodeGeneratorAgent extends Agent { }); } catch (error) { - this.logger().error('Error handling user input:', error); if (error instanceof RateLimitExceededError) { - this.logger().error('throwing Rate limit exceeded', error); + this.logger().error('Rate limit exceeded:', error); this.broadcast(WebSocketMessageResponses.RATE_LIMIT_ERROR, { error }); return; } - this.broadcast(WebSocketMessageResponses.ERROR, { - error: `Error processing user input: ${error instanceof Error ? error.message : String(error)}` - }); + this.broadcastError('Error processing user input', error); } } diff --git a/worker/agents/core/state.ts b/worker/agents/core/state.ts index 669dea8a..6d9d5342 100644 --- a/worker/agents/core/state.ts +++ b/worker/agents/core/state.ts @@ -1,4 +1,4 @@ -import type { Blueprint, ClientReportedErrorType, PhaseConceptType , +import type { Blueprint, PhaseConceptType , FileOutputType, } from '../schemas'; import type { TemplateDetails } from '../../services/sandbox/sandboxTypes'; @@ -40,7 +40,6 @@ export interface CodeGenState { sandboxInstanceId?: string; // previewURL?: string; // tunnelURL?: string; - clientReportedErrors: ClientReportedErrorType[]; // latestScreenshot?: ScreenshotData; // Store captured screenshot shouldBeGenerating: boolean; // Persistent flag indicating generation should be active mvpGenerated: boolean; diff --git a/worker/agents/core/types.ts b/worker/agents/core/types.ts index 91e5412b..55aee1d6 100644 --- a/worker/agents/core/types.ts +++ b/worker/agents/core/types.ts @@ -1,6 +1,6 @@ import type { RuntimeError, StaticAnalysisResponse } from '../../services/sandbox/sandboxTypes'; -import type { ClientReportedErrorType, FileOutputType, PhaseConceptType } from '../schemas'; +import type { FileOutputType, PhaseConceptType } from '../schemas'; import type { ConversationMessage } from '../inferutils/common'; import type { InferenceContext } from '../inferutils/config.types'; import type { TemplateDetails } from '../../services/sandbox/sandboxTypes'; @@ -26,7 +26,6 @@ export interface AgentInitArgs { export interface AllIssues { runtimeErrors: RuntimeError[]; staticAnalysis: StaticAnalysisResponse; - clientErrors: ClientReportedErrorType[]; } /** diff --git a/worker/agents/core/websocket.ts b/worker/agents/core/websocket.ts index 6aca79fe..78358c6d 100644 --- a/worker/agents/core/websocket.ts +++ b/worker/agents/core/websocket.ts @@ -104,10 +104,6 @@ export function handleWebSocketMessage(agent: SimpleCodeGeneratorAgent, connecti logger.error('Error during preview deployment:', error); }); break; - case WebSocketMessageRequests.RUNTIME_ERROR_FOUND: - logger.info(`Client reported errors: ${parsedMessage.data}`); - agent.setState({ ...agent.state, clientReportedErrors: parsedMessage.data }); - break; case WebSocketMessageRequests.CAPTURE_SCREENSHOT: agent.captureScreenshot(parsedMessage.data.url, parsedMessage.data.viewport).then((screenshotResult) => { if (!screenshotResult) { diff --git a/worker/agents/domain/values/IssueReport.ts b/worker/agents/domain/values/IssueReport.ts index 21ac4d3f..531ed375 100644 --- a/worker/agents/domain/values/IssueReport.ts +++ b/worker/agents/domain/values/IssueReport.ts @@ -1,6 +1,5 @@ import { RuntimeError, StaticAnalysisResponse } from '../../../services/sandbox/sandboxTypes'; import { AllIssues } from '../../core/types'; -import { ClientReportedErrorType } from '../../schemas'; /** * Immutable report of issues found during code generation @@ -10,13 +9,11 @@ export class IssueReport { constructor( public readonly runtimeErrors: RuntimeError[], public readonly staticAnalysis: StaticAnalysisResponse, - public readonly clientErrors: ClientReportedErrorType[] ) { // Freeze to ensure immutability Object.freeze(this); Object.freeze(this.runtimeErrors); Object.freeze(this.staticAnalysis); - Object.freeze(this.clientErrors); } /** @@ -26,7 +23,6 @@ export class IssueReport { return new IssueReport( issues.runtimeErrors || [], issues.staticAnalysis || { success: false, lint: { issues: [] }, typecheck: { issues: [] } }, - issues.clientErrors || [] ); } @@ -34,7 +30,7 @@ export class IssueReport { * Check if there are any issues */ hasIssues(): boolean { - return this.hasRuntimeErrors() || this.hasStaticAnalysisIssues() || this.hasClientErrors(); + return this.hasRuntimeErrors() || this.hasStaticAnalysisIssues(); } /** @@ -53,13 +49,6 @@ export class IssueReport { return lintIssues > 0 || typecheckIssues > 0; } - /** - * Check if there are client errors - */ - hasClientErrors(): boolean { - return this.clientErrors.length > 0; - } - /** * Get total issue count */ @@ -67,9 +56,8 @@ export class IssueReport { const runtimeCount = this.runtimeErrors.length; const lintCount = this.staticAnalysis.lint?.issues?.length || 0; const typecheckCount = this.staticAnalysis.typecheck?.issues?.length || 0; - const clientCount = this.clientErrors.length; - return runtimeCount + lintCount + typecheckCount + clientCount; + return runtimeCount + lintCount + typecheckCount; } /** @@ -92,10 +80,6 @@ export class IssueReport { parts.push(`${typecheckCount} type errors`); } - if (this.clientErrors.length > 0) { - parts.push(`${this.clientErrors.length} client errors`); - } - return parts.length > 0 ? parts.join(', ') : 'No issues found'; } @@ -106,7 +90,6 @@ export class IssueReport { return new IssueReport( [], { success: true, lint: { issues: [] }, typecheck: { issues: [] } }, - [] ); } } \ No newline at end of file diff --git a/worker/agents/prompts.ts b/worker/agents/prompts.ts index 4bee946c..d66b3df3 100644 --- a/worker/agents/prompts.ts +++ b/worker/agents/prompts.ts @@ -1,7 +1,7 @@ import { FileTreeNode, RuntimeError, StaticAnalysisResponse, TemplateDetails } from "../services/sandbox/sandboxTypes"; import { TemplateRegistry } from "./inferutils/schemaFormatters"; import z from 'zod'; -import { Blueprint, BlueprintSchema, ClientReportedErrorSchema, ClientReportedErrorType, FileOutputType, PhaseConceptSchema, PhaseConceptType, TemplateSelection } from "./schemas"; +import { Blueprint, BlueprintSchema, FileOutputType, PhaseConceptSchema, PhaseConceptType, TemplateSelection } from "./schemas"; import { IssueReport } from "./domain/values/IssueReport"; import { FileState, MAX_PHASES } from "./core/state"; import { CODE_SERIALIZERS, CodeSerializerType } from "./utils/codeSerializers"; @@ -132,18 +132,6 @@ ${lintOutput} ${typecheckOutput}`; }, - serializeClientReportedErrors(errors: ClientReportedErrorType[]): string { - if (errors && errors.length > 0) { - const errorsText = TemplateRegistry.markdown.serialize( - { errors }, - z.object({ errors: z.array(ClientReportedErrorSchema) }) - ); - return errorsText; - } else { - return 'No client-reported errors'; - } - }, - verifyPrompt(prompt: string): string { // If any of the '{{variables}}' are not replaced, throw an error // if (prompt.includes('{{')) { diff --git a/worker/agents/schemas.ts b/worker/agents/schemas.ts index 7409e3da..1b45f9fa 100644 --- a/worker/agents/schemas.ts +++ b/worker/agents/schemas.ts @@ -106,26 +106,6 @@ export const SetupCommandsSchema = z.object({ commands: z.array(z.string()).describe('Commands to set up the development environment and install all dependencies not already in the template. These will run before code generation starts.') }); -export const ClientReportedErrorSchema = z.object({ - type: z.string().describe('Type of error'), - data: z.object({ - errorType: z.string().describe('Type of error'), - consecutiveCount: z.number().describe('Number of consecutive errors'), - url: z.string().describe('URL where the error occurred'), - timestamp: z.string().describe('Timestamp of the error'), - error: z.object({ - message: z.string().describe('Error message'), - fullBodyText: z.string().describe('Full error body text'), - fullBodyHtml: z.string().describe('Full error body HTML'), - errorElementsFound: z.number().describe('Number of error elements found'), - }).describe('Error details'), - browserInfo: z.object({ - userAgent: z.string().describe('User agent'), - url: z.string().describe('URL where the error occurred'), - }).describe('Browser information'), - }).describe('Error data'), -}); - // Screenshot Analysis Schema export const ScreenshotAnalysisSchema = z.object({ hasIssues: z.boolean().describe('Whether any issues were found in the screenshot'), @@ -148,7 +128,6 @@ export type FileGenerationOutputType = z.infer; export type DocumentationOutputType = z.infer; export type CodeReviewOutputType = z.infer; export type SetupCommandsType = z.infer; -export type ClientReportedErrorType = z.infer; export type ScreenshotAnalysisType = z.infer; // Conversational AI Schemas diff --git a/worker/agents/services/implementations/BaseAgentService.ts b/worker/agents/services/implementations/BaseAgentService.ts new file mode 100644 index 00000000..6cb7410e --- /dev/null +++ b/worker/agents/services/implementations/BaseAgentService.ts @@ -0,0 +1,71 @@ +import { IStateManager } from '../interfaces/IStateManager'; +import { IFileManager } from '../interfaces/IFileManager'; +import { BaseSandboxService } from '../../../services/sandbox/BaseSandboxService'; +import { StructuredLogger } from '../../../logger'; +import { ServiceOptions } from '../interfaces/IServiceOptions'; + +/** + * Base class for all agent services + * Provides common dependencies and DO-compatible access patterns + */ +export abstract class BaseAgentService { + protected readonly stateManager: IStateManager; + protected readonly fileManager: IFileManager; + protected readonly getSandboxClient: () => BaseSandboxService; + protected readonly getLogger: () => StructuredLogger; + + constructor(options: ServiceOptions) { + this.stateManager = options.stateManager; + this.fileManager = options.fileManager; + this.getSandboxClient = options.getSandboxClient; + this.getLogger = options.getLogger; + } + + /** + * Get current agent state + */ + protected getState() { + return this.stateManager.getState(); + } + + /** + * Update agent state + */ + protected setState(newState: ReturnType) { + this.stateManager.setState(newState); + } + + /** + * Get fresh sandbox client instance (DO-compatible) + */ + protected getClient(): BaseSandboxService { + return this.getSandboxClient(); + } + + /** + * Get fresh logger instance (DO-compatible) + */ + protected getLog(): StructuredLogger { + return this.getLogger(); + } + + /** + * Execute an operation with a timeout + */ + protected async withTimeout( + operation: Promise, + timeoutMs: number, + errorMsg: string, + onTimeout?: () => void + ): Promise { + return Promise.race([ + operation, + new Promise((_, reject) => + setTimeout(() => { + onTimeout?.(); + reject(new Error(errorMsg)); + }, timeoutMs) + ) + ]); + } +} diff --git a/worker/agents/services/implementations/CommandManager.ts b/worker/agents/services/implementations/CommandManager.ts new file mode 100644 index 00000000..72ebc94a --- /dev/null +++ b/worker/agents/services/implementations/CommandManager.ts @@ -0,0 +1,94 @@ +import { ICommandManager } from '../interfaces/ICommandManager'; +import { BaseAgentService } from './BaseAgentService'; +import { ServiceOptions } from '../interfaces/IServiceOptions'; + +/** + * Manages simple command execution on sandbox instances + * For setup commands during redeployment + */ +export class CommandManager extends BaseAgentService implements ICommandManager { + constructor( + options: ServiceOptions, + private maxCommandsHistory: number + ) { + super(options); + } + + /** + * Execute setup commands (used during redeployment) + */ + async executeSetupCommands(sandboxInstanceId: string, timeoutMs: number = 60000): Promise { + const { commandsHistory } = this.getState(); + const logger = this.getLog(); + const client = this.getClient(); + + if (!commandsHistory || commandsHistory.length === 0) { + return; + } + + let cmds = commandsHistory; + if (cmds.length > this.maxCommandsHistory) { + // Deduplicate + cmds = Array.from(new Set(commandsHistory)); + } + + logger.info(`Executing ${cmds.length} setup commands on instance ${sandboxInstanceId}`); + + await this.withTimeout( + client.executeCommands(sandboxInstanceId, cmds), + timeoutMs, + 'Command execution timed out' + ); + + logger.info('Setup commands executed successfully'); + } + + /** + * Get command history + */ + getCommandHistory(): string[] { + const { commandsHistory } = this.getState(); + return commandsHistory || []; + } + + /** + * Add commands to history + */ + addToHistory(commands: string[]): void { + const state = this.getState(); + const logger = this.getLog(); + const currentHistory = state.commandsHistory || []; + + let combined = [...currentHistory, ...commands]; + + if (combined.length > this.maxCommandsHistory) { + // Deduplicate + combined = Array.from(new Set(combined)); + } + + this.setState({ + ...state, + commandsHistory: combined + }); + + logger.info(`Added ${commands.length} commands to history`, { + newHistorySize: combined.length, + maxSize: this.maxCommandsHistory + }); + } + + /** + * Clear command history + */ + clearHistory(): void { + const state = this.getState(); + const logger = this.getLog(); + + this.setState({ + ...state, + commandsHistory: [] + }); + + logger.info('Cleared command history'); + } +} diff --git a/worker/agents/services/implementations/DeploymentManager.ts b/worker/agents/services/implementations/DeploymentManager.ts new file mode 100644 index 00000000..a904121b --- /dev/null +++ b/worker/agents/services/implementations/DeploymentManager.ts @@ -0,0 +1,670 @@ +import { + IDeploymentManager, + DeploymentParams, + DeploymentResult, + SandboxDeploymentCallbacks, + CloudflareDeploymentCallbacks +} from '../interfaces/IDeploymentManager'; +import { BootstrapResponse, GitHubPushRequest, StaticAnalysisResponse, RuntimeError, PreviewType } from '../../../services/sandbox/sandboxTypes'; +import { GitHubExportResult } from '../../../services/github/types'; +import { FileOutputType } from '../../schemas'; +import { generateId } from '../../../utils/idGenerator'; +import { generateAppProxyToken, generateAppProxyUrl } from '../../../services/aigateway-proxy/controller'; +import { BaseAgentService } from './BaseAgentService'; +import { ServiceOptions } from '../interfaces/IServiceOptions'; + +const MAX_DEPLOYMENT_RETRIES = 3; +const DEPLOYMENT_TIMEOUT_MS = 60000; +const HEALTH_CHECK_INTERVAL_MS = 30000; + +/** + * Manages deployment operations for sandbox instances + * Handles instance creation, file deployment, analysis, and GitHub/Cloudflare export + * Also manages sessionId and health check intervals + */ +export class DeploymentManager extends BaseAgentService implements IDeploymentManager { + private healthCheckInterval: ReturnType | null = null; + private currentDeploymentPromise: Promise | null = null; + + constructor( + options: ServiceOptions, + private env: Env, + private projectNamePrefixMaxLength: number + ) { + super(options); + + // Ensure state has sessionId + const state = this.getState(); + if (!state.sessionId) { + this.setState({ + ...state, + sessionId: this.generateNewSessionId() + }); + } + } + + /** + * Get current session ID from state + */ + getSessionId(): string { + return this.getState().sessionId; + } + + /** + * Reset session ID (called on timeout or specific errors) + */ + resetSessionId(): void { + const logger = this.getLog(); + const state = this.getState(); + const oldSessionId = state.sessionId; + const newSessionId = this.generateNewSessionId(); + + logger.info(`SessionId reset: ${oldSessionId} → ${newSessionId}`); + + // Update state + this.setState({ + ...state, + sessionId: newSessionId, + sandboxInstanceId: undefined // Clear instance on session reset + }); + } + + private generateNewSessionId(): string { + return generateId(); + } + + /** + * Wait for preview to be ready + */ + async waitForPreview(): Promise { + const state = this.getState(); + const logger = this.getLog(); + + logger.info("Waiting for preview"); + + if (!state.sandboxInstanceId) { + logger.info("No sandbox instance, will create during next deploy"); + } + + logger.info("Waiting for preview completed"); + } + + /** + * Start health check interval for instance + */ + private startHealthCheckInterval(instanceId: string): void { + const logger = this.getLog(); + + // Clear any existing interval + this.clearHealthCheckInterval(); + + logger.info(`Starting health check interval for instance ${instanceId}`); + + this.healthCheckInterval = setInterval(async () => { + try { + const client = this.getClient(); + const status = await client.getInstanceStatus(instanceId); + + if (!status.success || !status.isHealthy) { + logger.warn(`Instance ${instanceId} unhealthy, clearing interval`); + this.clearHealthCheckInterval(); + } + } catch (error) { + logger.error('Health check failed:', error); + } + }, HEALTH_CHECK_INTERVAL_MS); + } + + private clearHealthCheckInterval(): void { + if (this.healthCheckInterval !== null) { + clearInterval(this.healthCheckInterval); + this.healthCheckInterval = null; + } + } + + /** + * Run static analysis (lint + typecheck) on code + */ + async runStaticAnalysis(files?: string[]): Promise { + const { sandboxInstanceId } = this.getState(); + + if (!sandboxInstanceId) { + throw new Error('No sandbox instance available for static analysis'); + } + + const logger = this.getLog(); + const client = this.getClient(); + + logger.info(`Linting code in sandbox instance ${sandboxInstanceId}`); + + const targetFiles = Array.isArray(files) && files.length > 0 + ? files + : this.fileManager.getGeneratedFilePaths(); + + const analysisResponse = await client.runStaticAnalysisCode( + sandboxInstanceId, + targetFiles + ); + + if (!analysisResponse || analysisResponse.error) { + const errorMsg = `Code linting failed: ${analysisResponse?.error || 'Unknown error'}`; + logger.error(errorMsg, { fullResponse: analysisResponse }); + throw new Error(errorMsg); + } + + const { lint, typecheck } = analysisResponse; + const { issues: lintIssues, summary: lintSummary } = lint; + const { issues: typeCheckIssues, summary: typeCheckSummary } = typecheck; + + logger.info(`Linting found ${lintIssues.length} issues: ` + + `${lintSummary?.errorCount || 0} errors, ` + + `${lintSummary?.warningCount || 0} warnings, ` + + `${lintSummary?.infoCount || 0} info`); + + logger.info(`Type checking found ${typeCheckIssues.length} issues: ` + + `${typeCheckSummary?.errorCount || 0} errors, ` + + `${typeCheckSummary?.warningCount || 0} warnings, ` + + `${typeCheckSummary?.infoCount || 0} info`); + + return analysisResponse; + } + + /** + * Fetch runtime errors from sandbox instance + */ + async fetchRuntimeErrors(clear: boolean = true): Promise { + const { sandboxInstanceId } = this.getState(); + const logger = this.getLog(); + const client = this.getClient(); + + if (!sandboxInstanceId) { + throw new Error('No sandbox instance available for runtime error fetching'); + } + + const resp = await client.getInstanceErrors(sandboxInstanceId, clear); + + if (!resp || !resp.success) { + throw new Error(`Failed to fetch runtime errors: ${resp?.error || 'Unknown error'}`); + } + + const errors = resp.errors || []; + + if (errors.length > 0) { + logger.info(`Found ${errors.length} runtime errors: ${errors.map(e => e.message).join(', ')}`); + } + + return errors; + } + + /** + * Main deployment method + * Callbacks allow agent to broadcast at the right times + */ + async deployToSandbox( + files: FileOutputType[] = [], + redeploy: boolean = false, + commitMessage?: string, + clearLogs: boolean = false, + callbacks?: SandboxDeploymentCallbacks + ): Promise { + const logger = this.getLog(); + + // Queue management - prevent concurrent deployments + if (this.currentDeploymentPromise) { + logger.info('Deployment already in progress, waiting for completion'); + try { + const result = await this.currentDeploymentPromise; + if (result) { + logger.info('Previous deployment completed successfully, returning its result'); + return result; + } + } catch (error) { + logger.warn('Previous deployment failed, proceeding with new deployment:', error); + } + return null; + } + + logger.info("Deploying to sandbox", { files: files.length, redeploy, commitMessage, sessionId: this.getSessionId() }); + + // Create deployment promise + this.currentDeploymentPromise = this.executeDeploymentWithRetry( + files, + redeploy, + commitMessage, + clearLogs, + MAX_DEPLOYMENT_RETRIES, + callbacks + ); + + try { + // Wrap with timeout + const result = await this.withTimeout( + this.currentDeploymentPromise, + DEPLOYMENT_TIMEOUT_MS, + 'Deployment timed out', + () => { + logger.warn('Deployment timed out, resetting sessionId to provision new sandbox instance'); + this.resetSessionId(); + } + ); + return result; + } finally { + this.currentDeploymentPromise = null; + } + } + + /** + * Execute deployment with retry logic + * Handles error-specific sessionId reset and exponential backoff + */ + private async executeDeploymentWithRetry( + files: FileOutputType[], + redeploy: boolean, + commitMessage: string | undefined, + clearLogs: boolean, + retries: number, + callbacks?: SandboxDeploymentCallbacks + ): Promise { + const logger = this.getLog(); + + try { + // Callback: deployment actually starting now + callbacks?.onStarted?.({ + message: "Deploying code to sandbox service", + files: files.map(f => ({ filePath: f.filePath })) + }); + + logger.info('Deploying code to sandbox service'); + + // Core deployment + const result = await this.deploy({ + files, + redeploy, + commitMessage, + clearLogs + }); + + // Start health check after successful deployment + if (result.redeployed) { + this.startHealthCheckInterval(result.sandboxInstanceId); + } + + const preview = { + runId: result.sandboxInstanceId, + previewURL: result.previewURL, + tunnelURL: result.tunnelURL + }; + + // Callback: deployment completed + callbacks?.onCompleted?.({ + message: "Deployment completed", + instanceId: preview.runId, + previewURL: preview.previewURL ?? '', + tunnelURL: preview.tunnelURL ?? '' + }); + + return preview; + } catch (error) { + logger.error("Error deploying to sandbox service:", error, { + sessionId: this.getSessionId(), + sandboxInstanceId: this.getState().sandboxInstanceId + }); + + const errorMsg = error instanceof Error ? error.message : String(error); + + // Handle specific errors that require session reset + if (errorMsg.includes('Network connection lost') || + errorMsg.includes('Container service disconnected') || + errorMsg.includes('Internal error in Durable Object storage')) { + logger.warn('Session-level error detected, resetting sessionId'); + this.resetSessionId(); + } + + // Clear instance ID from state + const state = this.getState(); + this.setState({ + ...state, + sandboxInstanceId: undefined + }); + + // Retry logic with exponential backoff + if (retries > 0) { + logger.info(`Retrying deployment, ${retries} attempts remaining`); + + // Exponential backoff + await new Promise(resolve => + setTimeout(resolve, Math.pow(2, MAX_DEPLOYMENT_RETRIES - retries) * 1000) + ); + + return this.executeDeploymentWithRetry( + files, + redeploy, + commitMessage, + clearLogs, + retries - 1, + callbacks + ); + } + + // Callback: deployment failed after all retries + logger.error('Deployment failed after all retries'); + callbacks?.onError?.({ + error: `Error deploying to sandbox service: ${errorMsg}. Please report an issue if this persists` + }); + + return null; + } + } + + /** + * Deploy files to sandbox instance (core deployment) + */ + private async deploy(params: DeploymentParams): Promise { + const { files, redeploy, commitMessage, clearLogs } = params; + const logger = this.getLog(); + const client = this.getClient(); + + logger.info("Deploying code to sandbox service"); + + // Ensure instance exists and is healthy + const instanceResult = await this.ensureInstance(redeploy); + const { sandboxInstanceId, previewURL, tunnelURL, redeployed } = instanceResult; + + // Determine which files to deploy + const filesToWrite = this.getFilesToDeploy(files, redeployed); + + // Write files if any + if (filesToWrite.length > 0) { + const writeResponse = await client.writeFiles( + sandboxInstanceId, + filesToWrite, + commitMessage + ); + + if (!writeResponse || !writeResponse.success) { + logger.error(`File writing failed. Error: ${writeResponse?.error}`); + throw new Error(`File writing failed. Error: ${writeResponse?.error}`); + } + } + + // Clear logs if requested + if (clearLogs) { + try { + logger.info('Clearing logs and runtime errors for instance', { instanceId: sandboxInstanceId }); + await Promise.all([ + client.getLogs(sandboxInstanceId, true), + client.clearInstanceErrors(sandboxInstanceId) + ]); + } catch (error) { + logger.error('Failed to clear logs and runtime errors', error); + } + } + + return { + sandboxInstanceId, + previewURL, + tunnelURL, + redeployed + }; + } + + /** + * Ensure sandbox instance exists and is healthy + */ + async ensureInstance(redeploy: boolean): Promise { + const state = this.getState(); + const { sandboxInstanceId } = state; + const logger = this.getLog(); + const client = this.getClient(); + + // Check existing instance if not forcing redeploy + if (sandboxInstanceId && !redeploy) { + const status = await client.getInstanceStatus(sandboxInstanceId); + if (status.success && status.isHealthy) { + logger.info(`DEPLOYMENT CHECK PASSED: Instance ${sandboxInstanceId} is running`); + return { + sandboxInstanceId, + previewURL: status.previewURL, + tunnelURL: status.tunnelURL, + redeployed: false + }; + } + logger.error(`DEPLOYMENT CHECK FAILED: Failed to get status for instance ${sandboxInstanceId}, redeploying...`); + } + + const results = await this.createNewInstance(); + if (!results || !results.runId || !results.previewURL) { + throw new Error('Failed to create new deployment'); + } + + // Update state with new instance ID + this.setState({ + ...state, + sandboxInstanceId: results.runId, + }); + + return { + sandboxInstanceId: results.runId, + previewURL: results.previewURL, + tunnelURL: results.tunnelURL, + redeployed: true + }; + } + + + /** + * Create new sandbox instance + */ + private async createNewInstance(): Promise { + const state = this.getState(); + const templateName = state.templateDetails?.name || 'scratch'; + + // Generate unique project name + let prefix = (state.blueprint?.projectName || templateName) + .toLowerCase() + .replace(/[^a-z0-9]/g, '-'); + const uniqueSuffix = generateId(); + prefix = prefix.slice(0, this.projectNamePrefixMaxLength); + const projectName = `${prefix}-${uniqueSuffix}`.toLowerCase(); + + // Webhook URL will be passed from agent + // Agent generates it using getProtocolForHost and getAgentId() + + // Add AI proxy vars if AI template + let localEnvVars: Record = {}; + if (state.templateDetails?.name?.includes('agents')) { + localEnvVars = { + "CF_AI_BASE_URL": generateAppProxyUrl(this.env), + "CF_AI_API_KEY": await generateAppProxyToken( + state.inferenceContext.agentId, + state.inferenceContext.userId, + this.env + ) + }; + } + + // Create instance + const client = this.getClient(); + const logger = this.getLog(); + + const createResponse = await client.createInstance( + templateName, + `v1-${projectName}`, + undefined, + localEnvVars + ); + + if (!createResponse || !createResponse.success || !createResponse.runId) { + throw new Error(`Failed to create sandbox instance: ${createResponse?.error || 'Unknown error'}`); + } + + logger.info(`Created sandbox instance`, { + runId: createResponse.runId, + previewURL: createResponse.previewURL + }); + + if (createResponse.runId && createResponse.previewURL) { + return createResponse; + } + + throw new Error(`Failed to create sandbox instance: ${createResponse?.error || 'Unknown error'}`); + } + + /** + * Determine which files to deploy + */ + private getFilesToDeploy( + requestedFiles: FileOutputType[], + redeployed: boolean + ): Array<{ filePath: string; fileContents: string }> { + const state = this.getState(); + + // If no files requested or redeploying, use all generated files from state + if (!requestedFiles || requestedFiles.length === 0 || redeployed) { + requestedFiles = Object.values(state.generatedFilesMap); + } + + return requestedFiles.map(file => ({ + filePath: file.filePath, + fileContents: file.fileContents + })); + } + + /** + * Deploy to Cloudflare Workers + * Returns deployment URL and deployment ID for database updates + */ + async deployToCloudflare(callbacks?: CloudflareDeploymentCallbacks): Promise<{ deploymentUrl: string | null; deploymentId?: string }> { + const state = this.getState(); + const logger = this.getLog(); + const client = this.getClient(); + + await this.waitForPreview(); + + callbacks?.onStarted?.({ + message: 'Starting deployment to Cloudflare Workers...', + instanceId: state.sandboxInstanceId ?? '' + }); + + logger.info('Starting Cloudflare deployment'); + + // Check if we have generated files + if (!state.generatedFilesMap || Object.keys(state.generatedFilesMap).length === 0) { + logger.error('No generated files available for deployment'); + callbacks?.onError?.({ + message: 'Deployment failed: No generated code available', + instanceId: state.sandboxInstanceId ?? '', + error: 'No files have been generated yet' + }); + return { deploymentUrl: null }; + } + + // Ensure sandbox instance exists - return null to trigger agent orchestration + if (!state.sandboxInstanceId) { + logger.info('No sandbox instance ID available'); + return { deploymentUrl: null }; + } + + logger.info('Prerequisites met, initiating deployment', { + sandboxInstanceId: state.sandboxInstanceId, + fileCount: Object.keys(state.generatedFilesMap).length + }); + + // Deploy to Cloudflare + const deploymentResult = await client.deployToCloudflareWorkers( + state.sandboxInstanceId + ); + + logger.info('Deployment result:', deploymentResult); + + if (!deploymentResult || !deploymentResult.success) { + logger.error('Deployment failed', { + message: deploymentResult?.message, + error: deploymentResult?.error + }); + + // Check for preview expired error + if (deploymentResult?.error?.includes('Failed to read instance metadata') || + deploymentResult?.error?.includes(`/bin/sh: 1: cd: can't cd to i-`)) { + logger.error('Deployment sandbox died - preview expired'); + callbacks?.onPreviewExpired?.(); + } else { + callbacks?.onError?.({ + message: `Deployment failed: ${deploymentResult?.message || 'Unknown error'}`, + instanceId: state.sandboxInstanceId ?? '', + error: deploymentResult?.error || 'Unknown deployment error' + }); + } + + return { deploymentUrl: null }; + } + + const deploymentUrl = deploymentResult.deployedUrl; + const deploymentId = deploymentResult.deploymentId; + + logger.info('Cloudflare deployment completed successfully', { + deploymentUrl, + deploymentId, + message: deploymentResult.message + }); + + callbacks?.onCompleted?.({ + message: deploymentResult.message || 'Successfully deployed to Cloudflare Workers!', + instanceId: state.sandboxInstanceId ?? '', + deploymentUrl: deploymentUrl || '' + }); + + return { + deploymentUrl: deploymentUrl || null, + deploymentId: deploymentId + }; + } + + /** + * Push to GitHub repository + */ + async pushToGitHub(options: GitHubPushRequest): Promise { + const state = this.getState(); + const logger = this.getLog(); + const client = this.getClient(); + + logger.info('Starting GitHub export', { + repositoryUrl: options.repositoryHtmlUrl, + fileCount: Object.keys(state.generatedFilesMap).length + }); + + // Check if we have generated files + if (!state.generatedFilesMap || Object.keys(state.generatedFilesMap).length === 0) { + throw new Error('No generated files available for export'); + } + + // Ensure sandbox instance exists + if (!state.sandboxInstanceId) { + throw new Error('No sandbox instance available'); + } + + const allFiles = this.fileManager.getGeneratedFiles(); + + // Push to GitHub + const exportResult = await client.pushToGitHub( + state.sandboxInstanceId, + options, + allFiles + ); + + if (!exportResult?.success) { + throw new Error(`Failed to export to GitHub repository: ${exportResult?.error}`); + } + + logger.info('GitHub export completed successfully', { + options, + commitSha: exportResult.commitSha + }); + + // Update readme with Cloudflare button if exists + // Note: This is handled by agent after this service returns + // Agent will redeploy and call this method again if needed + + return exportResult; + } +} diff --git a/worker/agents/services/interfaces/IAnalysisManager.ts b/worker/agents/services/interfaces/IAnalysisManager.ts new file mode 100644 index 00000000..e69de29b diff --git a/worker/agents/services/interfaces/ICommandManager.ts b/worker/agents/services/interfaces/ICommandManager.ts new file mode 100644 index 00000000..327efad7 --- /dev/null +++ b/worker/agents/services/interfaces/ICommandManager.ts @@ -0,0 +1,26 @@ +/** + * Interface for command execution operations + * Handles simple setup command execution and history tracking + * Note: Complex AI retry logic remains in agent + */ +export interface ICommandManager { + /** + * Execute setup commands during redeployment + */ + executeSetupCommands(sandboxInstanceId: string, timeoutMs?: number): Promise; + + /** + * Get command history + */ + getCommandHistory(): string[]; + + /** + * Add commands to history + */ + addToHistory(commands: string[]): void; + + /** + * Clear command history + */ + clearHistory(): void; +} diff --git a/worker/agents/services/interfaces/IDeploymentManager.ts b/worker/agents/services/interfaces/IDeploymentManager.ts new file mode 100644 index 00000000..3645ce7e --- /dev/null +++ b/worker/agents/services/interfaces/IDeploymentManager.ts @@ -0,0 +1,102 @@ +import { FileOutputType } from '../../schemas'; +import { GitHubPushRequest, StaticAnalysisResponse, RuntimeError, PreviewType } from '../../../services/sandbox/sandboxTypes'; +import { GitHubExportResult } from '../../../services/github/types'; +import { DeploymentStartedMessage, DeploymentCompletedMessage, DeploymentFailedMessage } from '../../../api/websocketTypes'; +import { CloudflareDeploymentStartedMessage, CloudflareDeploymentCompletedMessage, CloudflareDeploymentErrorMessage } from '../../../api/websocketTypes'; + +/** + * Callbacks for sandbox deployment events + */ +export interface SandboxDeploymentCallbacks { + onStarted?: (data: Omit) => void; + onCompleted?: (data: Omit) => void; + onError?: (data: Omit) => void; +} + +/** + * Callbacks for Cloudflare deployment events + */ +export interface CloudflareDeploymentCallbacks { + onStarted?: (data: Omit) => void; + onCompleted?: (data: Omit) => void; + onError?: (data: Omit) => void; + onPreviewExpired?: () => void; +} + +/** + * Parameters for deployment operation (internal use) + */ +export interface DeploymentParams { + files: FileOutputType[]; + redeploy: boolean; + commitMessage?: string; + clearLogs?: boolean; +} + +/** + * Result from deployment/instance operations (internal use) + */ +export interface DeploymentResult { + sandboxInstanceId: string; + previewURL?: string; + tunnelURL?: string; + redeployed: boolean; +} + +/** + * Interface for deployment management operations + * Handles sandbox deployment, instance creation, analysis, and exports + * Manages sessionId and health check intervals internally + */ +export interface IDeploymentManager { + /** + * Get current session ID + */ + getSessionId(): string; + + /** + * Reset session ID (called on timeout or specific errors) + */ + resetSessionId(): void; + + /** + * Run static analysis (lint + typecheck) on code + * Merged from AnalysisManager + */ + runStaticAnalysis(files?: string[]): Promise; + + /** + * Fetch runtime errors from sandbox instance + * Merged from AnalysisManager + */ + fetchRuntimeErrors(clear?: boolean): Promise; + + /** + * Wait for preview to be ready (used during initialization) + */ + waitForPreview(): Promise; + + /** + * Deploy to sandbox with full orchestration + * Handles: queue, retry, timeout, sessionId reset, health checks + * Callbacks allow agent to broadcast at the right times (after queue, when actually starting) + */ + deployToSandbox( + files?: FileOutputType[], + redeploy?: boolean, + commitMessage?: string, + clearLogs?: boolean, + callbacks?: SandboxDeploymentCallbacks + ): Promise; + + /** + * Deploy to Cloudflare Workers + * Returns deployment URL and deployment ID for database updates + */ + deployToCloudflare(callbacks?: CloudflareDeploymentCallbacks): Promise<{ deploymentUrl: string | null; deploymentId?: string }>; + + /** + * Push to GitHub repository (handles both new and existing repos) + */ + pushToGitHub(options: GitHubPushRequest): Promise; +} diff --git a/worker/agents/services/interfaces/IServiceOptions.ts b/worker/agents/services/interfaces/IServiceOptions.ts new file mode 100644 index 00000000..6b627808 --- /dev/null +++ b/worker/agents/services/interfaces/IServiceOptions.ts @@ -0,0 +1,14 @@ +import { IStateManager } from './IStateManager'; +import { IFileManager } from './IFileManager'; +import { BaseSandboxService } from '../../../services/sandbox/BaseSandboxService'; +import { StructuredLogger } from '../../../logger'; + +/** + * Common options for all agent services + */ +export interface ServiceOptions { + stateManager: IStateManager; + fileManager: IFileManager; + getSandboxClient: () => BaseSandboxService; + getLogger: () => StructuredLogger; +} diff --git a/worker/api/websocketTypes.ts b/worker/api/websocketTypes.ts index f02f07b3..3208d768 100644 --- a/worker/api/websocketTypes.ts +++ b/worker/api/websocketTypes.ts @@ -1,4 +1,4 @@ -import type { ClientReportedErrorType, CodeReviewOutputType, FileConceptType, FileOutputType } from "../agents/schemas"; +import type { CodeReviewOutputType, FileConceptType, FileOutputType } from "../agents/schemas"; import type { CodeGenState } from "../agents/core/state"; import type { ConversationState } from "../agents/inferutils/common"; import type { CodeIssue, RuntimeError, StaticAnalysisResponse } from "../services/sandbox/sandboxTypes"; @@ -67,18 +67,18 @@ type GenerationCompleteMessage = { previewURL?: string; }; -type DeploymentStartedMessage = { +export type DeploymentStartedMessage = { type: 'deployment_started'; message: string; files: { filePath: string }[]; }; -type DeploymentFailedMessage = { +export type DeploymentFailedMessage = { type: 'deployment_failed'; error: string; }; -type DeploymentCompletedMessage = { +export type DeploymentCompletedMessage = { type: 'deployment_completed'; previewURL: string; tunnelURL: string; @@ -114,7 +114,6 @@ type CodeReviewingMessage = { type: 'code_reviewing'; message: string; staticAnalysis?: StaticAnalysisResponse; - clientErrors: ClientReportedErrorType[]; runtimeErrors: RuntimeError[]; }; @@ -217,13 +216,13 @@ type GenerationResumedMessage = { instanceId: string; }; -type CloudflareDeploymentStartedMessage = { +export type CloudflareDeploymentStartedMessage = { type: 'cloudflare_deployment_started'; message: string; instanceId: string; }; -type CloudflareDeploymentCompletedMessage = { +export type CloudflareDeploymentCompletedMessage = { type: 'cloudflare_deployment_completed'; message: string; instanceId: string; @@ -231,7 +230,7 @@ type CloudflareDeploymentCompletedMessage = { workersUrl?: string; }; -type CloudflareDeploymentErrorMessage = { +export type CloudflareDeploymentErrorMessage = { type: 'cloudflare_deployment_error'; message: string; instanceId: string; diff --git a/worker/services/sandbox/BaseSandboxService.ts b/worker/services/sandbox/BaseSandboxService.ts index dbe5d70f..be9c7fc8 100644 --- a/worker/services/sandbox/BaseSandboxService.ts +++ b/worker/services/sandbox/BaseSandboxService.ts @@ -189,7 +189,7 @@ import { FileOutputType } from 'worker/agents/schemas'; * Get all runtime errors from an instance * Returns: { success: boolean, errors: [...], hasErrors: boolean, error?: string } */ - abstract getInstanceErrors(instanceId: string): Promise; + abstract getInstanceErrors(instanceId: string, clear?: boolean): Promise; /** * Clear all runtime errors from an instance From 564c8ccccfba5541d2566dbfb922c5d359fff774 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sun, 19 Oct 2025 02:54:51 -0400 Subject: [PATCH 039/150] refactor: consolidate sandbox client management into DeploymentManager service --- worker/agents/core/simpleGeneratorAgent.ts | 219 ++++++++---------- .../implementations/BaseAgentService.ts | 14 +- .../implementations/CommandManager.ts | 94 -------- .../implementations/DeploymentManager.ts | 62 ++++- .../services/interfaces/ICommandManager.ts | 26 --- .../services/interfaces/IDeploymentManager.ts | 15 +- .../services/interfaces/IServiceOptions.ts | 3 +- 7 files changed, 168 insertions(+), 265 deletions(-) delete mode 100644 worker/agents/services/implementations/CommandManager.ts delete mode 100644 worker/agents/services/interfaces/ICommandManager.ts diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index 1910a064..e6a4acaa 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -18,9 +18,7 @@ import { ProjectSetupAssistant } from '../assistants/projectsetup'; import { UserConversationProcessor, RenderToolCall } from '../operations/UserConversationProcessor'; import { FileManager } from '../services/implementations/FileManager'; import { StateManager } from '../services/implementations/StateManager'; -import { CommandManager } from '../services/implementations/CommandManager'; import { DeploymentManager } from '../services/implementations/DeploymentManager'; -import { ServiceOptions } from '../services/interfaces/IServiceOptions'; // import { WebSocketBroadcaster } from '../services/implementations/WebSocketBroadcaster'; import { GenerationContext } from '../domain/values/GenerationContext'; import { IssueReport } from '../domain/values/IssueReport'; @@ -31,7 +29,6 @@ import { PhaseGenerationOperation } from '../operations/PhaseGeneration'; import { ScreenshotAnalysisOperation } from '../operations/ScreenshotAnalysis'; // Database schema imports removed - using zero-storage OAuth flow import { BaseSandboxService } from '../../services/sandbox/BaseSandboxService'; -import { getSandboxService } from '../../services/sandbox/factory'; import { WebSocketMessageData, WebSocketMessageType } from '../../api/websocketTypes'; import { InferenceContext, AgentActionKey } from '../inferutils/config.types'; import { AGENT_CONFIG } from '../inferutils/config'; @@ -65,7 +62,7 @@ interface Operations { const DEFAULT_CONVERSATION_SESSION_ID = 'default'; /** - * SimpleCodeGeneratorAgent - Deterministically orhestrated agent + * SimpleCodeGeneratorAgent - Deterministically orchestrated agent * * Manages the lifecycle of code generation including: * - Blueprint, phase generation, phase implementation, review cycles orchestrations @@ -78,13 +75,10 @@ export class SimpleCodeGeneratorAgent extends Agent { private static readonly PROJECT_NAME_PREFIX_MAX_LENGTH = 20; protected projectSetupAssistant: ProjectSetupAssistant | undefined; - protected sandboxServiceClient: BaseSandboxService | undefined; protected stateManager!: StateManager; protected fileManager!: FileManager; protected codingAgent: CodingAgentInterface = new CodingAgentInterface(this); - // Service layer for business logic - protected commandManager!: CommandManager; protected deploymentManager!: DeploymentManager; private previewUrlCache: string = ''; @@ -154,59 +148,6 @@ export class SimpleCodeGeneratorAgent extends Agent { lastDeepDebugTranscript: null, }; - /* - * Each DO has 10 gb of sqlite storage. However, the way agents sdk works, it stores the 'state' object of the agent as a single row - * in the cf_agents_state table. And row size has a much smaller limit in sqlite. Thus, we only keep current compactified conversation - * in the agent's core state and store the full conversation in a separate DO table. - */ - getConversationState(id: string = DEFAULT_CONVERSATION_SESSION_ID): ConversationState { - const currentConversation = this.state.conversationMessages; - const rows = this.sql<{ messages: string, id: string }>`SELECT * FROM full_conversations WHERE id = ${id}`; - let fullHistory: ConversationMessage[] = []; - if (rows.length > 0 && rows[0].messages) { - try { - const parsed = JSON.parse(rows[0].messages); - if (Array.isArray(parsed)) { - fullHistory = parsed as ConversationMessage[]; - } - } catch (_e) {} - } - if (fullHistory.length === 0) { - fullHistory = currentConversation; - } - // Load compact (running) history from sqlite with fallback to in-memory state for migration - const compactRows = this.sql<{ messages: string, id: string }>`SELECT * FROM compact_conversations WHERE id = ${id}`; - let runningHistory: ConversationMessage[] = []; - if (compactRows.length > 0 && compactRows[0].messages) { - try { - const parsed = JSON.parse(compactRows[0].messages); - if (Array.isArray(parsed)) { - runningHistory = parsed as ConversationMessage[]; - } - } catch (_e) {} - } - if (runningHistory.length === 0) { - runningHistory = currentConversation; - } - return { - id: id, - runningHistory, - fullHistory, - }; - } - - setConversationState(conversations: ConversationState) { - const serializedFull = JSON.stringify(conversations.fullHistory); - const serializedCompact = JSON.stringify(conversations.runningHistory); - try { - this.logger().info(`Saving conversation state ${conversations.id}, full_length: ${serializedFull.length}, compact_length: ${serializedCompact.length}`); - this.sql`INSERT OR REPLACE INTO compact_conversations (id, messages) VALUES (${conversations.id}, ${serializedCompact})`; - this.sql`INSERT OR REPLACE INTO full_conversations (id, messages) VALUES (${conversations.id}, ${serializedFull})`; - } catch (error) { - this.logger().error(`Failed to save conversation state ${conversations.id}`, error); - } - } - constructor(ctx: AgentContext, env: Env) { super(ctx, env); this.sql`CREATE TABLE IF NOT EXISTS full_conversations (id TEXT PRIMARY KEY, messages TEXT)`; @@ -221,51 +162,20 @@ export class SimpleCodeGeneratorAgent extends Agent { // Initialize FileManager this.fileManager = new FileManager(this.stateManager); - // Initialize service layer - const serviceOptions: ServiceOptions = { - stateManager: this.stateManager, - fileManager: this.fileManager, - getSandboxClient: () => this.getSandboxServiceClient(), - getLogger: () => this.logger() - }; - - this.commandManager = new CommandManager( - serviceOptions, - SimpleCodeGeneratorAgent.MAX_COMMANDS_HISTORY - ); + // Initialize DeploymentManager first (manages sandbox client caching) + // DeploymentManager will use its own getClient() override for caching this.deploymentManager = new DeploymentManager( - serviceOptions, - this.env, - SimpleCodeGeneratorAgent.PROJECT_NAME_PREFIX_MAX_LENGTH + { + stateManager: this.stateManager, + fileManager: this.fileManager, + getLogger: () => this.logger(), + env: this.env + }, + SimpleCodeGeneratorAgent.PROJECT_NAME_PREFIX_MAX_LENGTH, + SimpleCodeGeneratorAgent.MAX_COMMANDS_HISTORY ); } - async saveToDatabase() { - this.logger().info(`Blueprint generated successfully for agent ${this.getAgentId()}`); - // Save the app to database (authenticated users only) - const appService = new AppService(this.env); - await appService.createApp({ - id: this.state.inferenceContext.agentId, - userId: this.state.inferenceContext.userId, - sessionToken: null, - title: this.state.blueprint.title || this.state.query.substring(0, 100), - description: this.state.blueprint.description || null, - originalPrompt: this.state.query, - finalPrompt: this.state.query, - framework: this.state.blueprint.frameworks?.[0], - visibility: 'private', - status: 'generating', - createdAt: new Date(), - updatedAt: new Date() - }); - this.logger().info(`App saved successfully to database for agent ${this.state.inferenceContext.agentId}`, { - agentId: this.state.inferenceContext.agentId, - userId: this.state.inferenceContext.userId, - visibility: 'private' - }); - this.logger().info(`Agent initialized successfully for agent ${this.state.inferenceContext.agentId}`); - } - /** * Initialize the code generator with project blueprint and template * Sets up services and begins deployment process @@ -344,6 +254,85 @@ export class SimpleCodeGeneratorAgent extends Agent { async isInitialized() { return this.getAgentId() ? true : false } + + /* + * Each DO has 10 gb of sqlite storage. However, the way agents sdk works, it stores the 'state' object of the agent as a single row + * in the cf_agents_state table. And row size has a much smaller limit in sqlite. Thus, we only keep current compactified conversation + * in the agent's core state and store the full conversation in a separate DO table. + */ + getConversationState(id: string = DEFAULT_CONVERSATION_SESSION_ID): ConversationState { + const currentConversation = this.state.conversationMessages; + const rows = this.sql<{ messages: string, id: string }>`SELECT * FROM full_conversations WHERE id = ${id}`; + let fullHistory: ConversationMessage[] = []; + if (rows.length > 0 && rows[0].messages) { + try { + const parsed = JSON.parse(rows[0].messages); + if (Array.isArray(parsed)) { + fullHistory = parsed as ConversationMessage[]; + } + } catch (_e) {} + } + if (fullHistory.length === 0) { + fullHistory = currentConversation; + } + // Load compact (running) history from sqlite with fallback to in-memory state for migration + const compactRows = this.sql<{ messages: string, id: string }>`SELECT * FROM compact_conversations WHERE id = ${id}`; + let runningHistory: ConversationMessage[] = []; + if (compactRows.length > 0 && compactRows[0].messages) { + try { + const parsed = JSON.parse(compactRows[0].messages); + if (Array.isArray(parsed)) { + runningHistory = parsed as ConversationMessage[]; + } + } catch (_e) {} + } + if (runningHistory.length === 0) { + runningHistory = currentConversation; + } + return { + id: id, + runningHistory, + fullHistory, + }; + } + + setConversationState(conversations: ConversationState) { + const serializedFull = JSON.stringify(conversations.fullHistory); + const serializedCompact = JSON.stringify(conversations.runningHistory); + try { + this.logger().info(`Saving conversation state ${conversations.id}, full_length: ${serializedFull.length}, compact_length: ${serializedCompact.length}`); + this.sql`INSERT OR REPLACE INTO compact_conversations (id, messages) VALUES (${conversations.id}, ${serializedCompact})`; + this.sql`INSERT OR REPLACE INTO full_conversations (id, messages) VALUES (${conversations.id}, ${serializedFull})`; + } catch (error) { + this.logger().error(`Failed to save conversation state ${conversations.id}`, error); + } + } + + async saveToDatabase() { + this.logger().info(`Blueprint generated successfully for agent ${this.getAgentId()}`); + // Save the app to database (authenticated users only) + const appService = new AppService(this.env); + await appService.createApp({ + id: this.state.inferenceContext.agentId, + userId: this.state.inferenceContext.userId, + sessionToken: null, + title: this.state.blueprint.title || this.state.query.substring(0, 100), + description: this.state.blueprint.description || null, + originalPrompt: this.state.query, + finalPrompt: this.state.query, + framework: this.state.blueprint.frameworks?.[0], + visibility: 'private', + status: 'generating', + createdAt: new Date(), + updatedAt: new Date() + }); + this.logger().info(`App saved successfully to database for agent ${this.state.inferenceContext.agentId}`, { + agentId: this.state.inferenceContext.agentId, + userId: this.state.inferenceContext.userId, + visibility: 'private' + }); + this.logger().info(`Agent initialized successfully for agent ${this.state.inferenceContext.agentId}`); + } onStateUpdate(_state: CodeGenState, _source: "server" | Connection) {} @@ -378,16 +367,11 @@ export class SimpleCodeGeneratorAgent extends Agent { } getSessionId() { - // Delegate to deploymentManager which now manages sessionId return this.deploymentManager.getSessionId(); } getSandboxServiceClient(): BaseSandboxService { - if (this.sandboxServiceClient === undefined) { - this.logger().info('Initializing sandbox service client'); - this.sandboxServiceClient = getSandboxService(this.getSessionId(), this.getAgentId()); - } - return this.sandboxServiceClient; + return this.deploymentManager.getClient(); } isCodeGenerating(): boolean { @@ -1667,18 +1651,6 @@ export class SimpleCodeGeneratorAgent extends Agent { }; } - async waitForPreview(): Promise { - this.logger().info("Waiting for preview"); - if (!this.state.sandboxInstanceId) { - const preview = await this.deployToSandbox(); - if (!preview) { - this.logger().error("Failed create preview"); - return; - } - } - this.logger().info("Waiting for preview completed"); - } - async deployToSandbox(files: FileOutputType[] = [], redeploy: boolean = false, commitMessage?: string, clearLogs: boolean = false): Promise { // Call deployment manager with callbacks for broadcasting at the right times const result = await this.deploymentManager.deployToSandbox( @@ -1788,6 +1760,9 @@ export class SimpleCodeGeneratorAgent extends Agent { this.logger().info("Deep debug session completed successfully"); } catch (error) { this.logger().error("Error during deep debug session:", error); + } finally { + // Clear promise after waiting completes + this.deepDebugPromise = null; } } } @@ -1958,8 +1933,10 @@ export class SimpleCodeGeneratorAgent extends Agent { this.logger().info(`All commands executed successfully: ${successfulCommands.join(", ")}`); } - // Add commands to history via service - this.commandManager.addToHistory(successfulCommands); + this.setState({ + ...this.state, + commandsHistory: [...(this.state.commandsHistory || []), ...successfulCommands] + }); } async getLogs(_reset?: boolean, durationSeconds?: number): Promise { diff --git a/worker/agents/services/implementations/BaseAgentService.ts b/worker/agents/services/implementations/BaseAgentService.ts index 6cb7410e..a6ea39b1 100644 --- a/worker/agents/services/implementations/BaseAgentService.ts +++ b/worker/agents/services/implementations/BaseAgentService.ts @@ -1,6 +1,5 @@ import { IStateManager } from '../interfaces/IStateManager'; import { IFileManager } from '../interfaces/IFileManager'; -import { BaseSandboxService } from '../../../services/sandbox/BaseSandboxService'; import { StructuredLogger } from '../../../logger'; import { ServiceOptions } from '../interfaces/IServiceOptions'; @@ -11,14 +10,14 @@ import { ServiceOptions } from '../interfaces/IServiceOptions'; export abstract class BaseAgentService { protected readonly stateManager: IStateManager; protected readonly fileManager: IFileManager; - protected readonly getSandboxClient: () => BaseSandboxService; protected readonly getLogger: () => StructuredLogger; + protected readonly env: Env; constructor(options: ServiceOptions) { this.stateManager = options.stateManager; this.fileManager = options.fileManager; - this.getSandboxClient = options.getSandboxClient; this.getLogger = options.getLogger; + this.env = options.env; } /** @@ -35,13 +34,10 @@ export abstract class BaseAgentService { this.stateManager.setState(newState); } - /** - * Get fresh sandbox client instance (DO-compatible) - */ - protected getClient(): BaseSandboxService { - return this.getSandboxClient(); + getAgentId() { + return this.getState().inferenceContext.agentId } - + /** * Get fresh logger instance (DO-compatible) */ diff --git a/worker/agents/services/implementations/CommandManager.ts b/worker/agents/services/implementations/CommandManager.ts deleted file mode 100644 index 72ebc94a..00000000 --- a/worker/agents/services/implementations/CommandManager.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { ICommandManager } from '../interfaces/ICommandManager'; -import { BaseAgentService } from './BaseAgentService'; -import { ServiceOptions } from '../interfaces/IServiceOptions'; - -/** - * Manages simple command execution on sandbox instances - * For setup commands during redeployment - */ -export class CommandManager extends BaseAgentService implements ICommandManager { - constructor( - options: ServiceOptions, - private maxCommandsHistory: number - ) { - super(options); - } - - /** - * Execute setup commands (used during redeployment) - */ - async executeSetupCommands(sandboxInstanceId: string, timeoutMs: number = 60000): Promise { - const { commandsHistory } = this.getState(); - const logger = this.getLog(); - const client = this.getClient(); - - if (!commandsHistory || commandsHistory.length === 0) { - return; - } - - let cmds = commandsHistory; - if (cmds.length > this.maxCommandsHistory) { - // Deduplicate - cmds = Array.from(new Set(commandsHistory)); - } - - logger.info(`Executing ${cmds.length} setup commands on instance ${sandboxInstanceId}`); - - await this.withTimeout( - client.executeCommands(sandboxInstanceId, cmds), - timeoutMs, - 'Command execution timed out' - ); - - logger.info('Setup commands executed successfully'); - } - - /** - * Get command history - */ - getCommandHistory(): string[] { - const { commandsHistory } = this.getState(); - return commandsHistory || []; - } - - /** - * Add commands to history - */ - addToHistory(commands: string[]): void { - const state = this.getState(); - const logger = this.getLog(); - const currentHistory = state.commandsHistory || []; - - let combined = [...currentHistory, ...commands]; - - if (combined.length > this.maxCommandsHistory) { - // Deduplicate - combined = Array.from(new Set(combined)); - } - - this.setState({ - ...state, - commandsHistory: combined - }); - - logger.info(`Added ${commands.length} commands to history`, { - newHistorySize: combined.length, - maxSize: this.maxCommandsHistory - }); - } - - /** - * Clear command history - */ - clearHistory(): void { - const state = this.getState(); - const logger = this.getLog(); - - this.setState({ - ...state, - commandsHistory: [] - }); - - logger.info('Cleared command history'); - } -} diff --git a/worker/agents/services/implementations/DeploymentManager.ts b/worker/agents/services/implementations/DeploymentManager.ts index a904121b..d30bdf8b 100644 --- a/worker/agents/services/implementations/DeploymentManager.ts +++ b/worker/agents/services/implementations/DeploymentManager.ts @@ -12,6 +12,8 @@ import { generateId } from '../../../utils/idGenerator'; import { generateAppProxyToken, generateAppProxyUrl } from '../../../services/aigateway-proxy/controller'; import { BaseAgentService } from './BaseAgentService'; import { ServiceOptions } from '../interfaces/IServiceOptions'; +import { BaseSandboxService } from '../../../services/sandbox/BaseSandboxService'; +import { getSandboxService } from '../../../services/sandbox/factory'; const MAX_DEPLOYMENT_RETRIES = 3; const DEPLOYMENT_TIMEOUT_MS = 60000; @@ -25,11 +27,12 @@ const HEALTH_CHECK_INTERVAL_MS = 30000; export class DeploymentManager extends BaseAgentService implements IDeploymentManager { private healthCheckInterval: ReturnType | null = null; private currentDeploymentPromise: Promise | null = null; + private cachedSandboxClient: BaseSandboxService | null = null; constructor( options: ServiceOptions, - private env: Env, - private projectNamePrefixMaxLength: number + private projectNamePrefixMaxLength: number, + private maxCommandsHistory: number ) { super(options); @@ -50,6 +53,24 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa return this.getState().sessionId; } + /** + * Cache is tied to current sessionId and invalidated on reset + */ + public getClient(): BaseSandboxService { + if (!this.cachedSandboxClient) { + const logger = this.getLog(); + logger.info('Creating sandbox service client', { + sessionId: this.getSessionId(), + agentId: this.getAgentId() + }); + this.cachedSandboxClient = getSandboxService( + this.getSessionId(), + this.getAgentId() + ); + } + return this.cachedSandboxClient; + } + /** * Reset session ID (called on timeout or specific errors) */ @@ -61,6 +82,9 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa logger.info(`SessionId reset: ${oldSessionId} → ${newSessionId}`); + // Invalidate cached sandbox client (tied to old sessionId) + this.cachedSandboxClient = null; + // Update state this.setState({ ...state, @@ -89,6 +113,35 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa logger.info("Waiting for preview completed"); } + /** + * Execute setup commands (used during redeployment) + */ + async executeSetupCommands(sandboxInstanceId: string, timeoutMs: number = 60000): Promise { + const { commandsHistory } = this.getState(); + const logger = this.getLog(); + const client = this.getClient(); + + if (!commandsHistory || commandsHistory.length === 0) { + return; + } + + let cmds = commandsHistory; + if (cmds.length > this.maxCommandsHistory) { + // Deduplicate + cmds = Array.from(new Set(commandsHistory)); + } + + logger.info(`Executing ${cmds.length} setup commands on instance ${sandboxInstanceId}`); + + await this.withTimeout( + client.executeCommands(sandboxInstanceId, cmds), + timeoutMs, + 'Command execution timed out' + ); + + logger.info('Setup commands executed successfully'); + } + /** * Start health check interval for instance */ @@ -660,11 +713,6 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa options, commitSha: exportResult.commitSha }); - - // Update readme with Cloudflare button if exists - // Note: This is handled by agent after this service returns - // Agent will redeploy and call this method again if needed - return exportResult; } } diff --git a/worker/agents/services/interfaces/ICommandManager.ts b/worker/agents/services/interfaces/ICommandManager.ts deleted file mode 100644 index 327efad7..00000000 --- a/worker/agents/services/interfaces/ICommandManager.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Interface for command execution operations - * Handles simple setup command execution and history tracking - * Note: Complex AI retry logic remains in agent - */ -export interface ICommandManager { - /** - * Execute setup commands during redeployment - */ - executeSetupCommands(sandboxInstanceId: string, timeoutMs?: number): Promise; - - /** - * Get command history - */ - getCommandHistory(): string[]; - - /** - * Add commands to history - */ - addToHistory(commands: string[]): void; - - /** - * Clear command history - */ - clearHistory(): void; -} diff --git a/worker/agents/services/interfaces/IDeploymentManager.ts b/worker/agents/services/interfaces/IDeploymentManager.ts index 3645ce7e..8384bd5f 100644 --- a/worker/agents/services/interfaces/IDeploymentManager.ts +++ b/worker/agents/services/interfaces/IDeploymentManager.ts @@ -24,7 +24,7 @@ export interface CloudflareDeploymentCallbacks { } /** - * Parameters for deployment operation (internal use) + * Parameters for deployment operation */ export interface DeploymentParams { files: FileOutputType[]; @@ -34,7 +34,7 @@ export interface DeploymentParams { } /** - * Result from deployment/instance operations (internal use) + * Result from deployment/instance operations */ export interface DeploymentResult { sandboxInstanceId: string; @@ -61,13 +61,11 @@ export interface IDeploymentManager { /** * Run static analysis (lint + typecheck) on code - * Merged from AnalysisManager */ runStaticAnalysis(files?: string[]): Promise; /** * Fetch runtime errors from sandbox instance - * Merged from AnalysisManager */ fetchRuntimeErrors(clear?: boolean): Promise; @@ -77,7 +75,12 @@ export interface IDeploymentManager { waitForPreview(): Promise; /** - * Deploy to sandbox with full orchestration + * Execute setup commands during redeployment + */ + executeSetupCommands(sandboxInstanceId: string, timeoutMs?: number): Promise; + + /** + * Deploy to sandbox * Handles: queue, retry, timeout, sessionId reset, health checks * Callbacks allow agent to broadcast at the right times (after queue, when actually starting) */ @@ -96,7 +99,7 @@ export interface IDeploymentManager { deployToCloudflare(callbacks?: CloudflareDeploymentCallbacks): Promise<{ deploymentUrl: string | null; deploymentId?: string }>; /** - * Push to GitHub repository (handles both new and existing repos) + * Push to GitHub repository */ pushToGitHub(options: GitHubPushRequest): Promise; } diff --git a/worker/agents/services/interfaces/IServiceOptions.ts b/worker/agents/services/interfaces/IServiceOptions.ts index 6b627808..aa5275f7 100644 --- a/worker/agents/services/interfaces/IServiceOptions.ts +++ b/worker/agents/services/interfaces/IServiceOptions.ts @@ -1,14 +1,13 @@ import { IStateManager } from './IStateManager'; import { IFileManager } from './IFileManager'; -import { BaseSandboxService } from '../../../services/sandbox/BaseSandboxService'; import { StructuredLogger } from '../../../logger'; /** * Common options for all agent services */ export interface ServiceOptions { + env: Env, stateManager: IStateManager; fileManager: IFileManager; - getSandboxClient: () => BaseSandboxService; getLogger: () => StructuredLogger; } From 3898da495ec78526b36bf8c6657d5b89d45cefac Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sun, 19 Oct 2025 02:59:09 -0400 Subject: [PATCH 040/150] fix: build errors --- src/api-types.ts | 1 - src/routes/chat/utils/handle-websocket-message.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/api-types.ts b/src/api-types.ts index 83abc126..60462b39 100644 --- a/src/api-types.ts +++ b/src/api-types.ts @@ -137,7 +137,6 @@ export type { // Agent/Generator Types export type { Blueprint as BlueprintType, - ClientReportedErrorType, CodeReviewOutputType, FileConceptType, FileOutputType as GeneratedFile, diff --git a/src/routes/chat/utils/handle-websocket-message.ts b/src/routes/chat/utils/handle-websocket-message.ts index 97abe729..dd93d9db 100644 --- a/src/routes/chat/utils/handle-websocket-message.ts +++ b/src/routes/chat/utils/handle-websocket-message.ts @@ -490,7 +490,6 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { `Lint Issues: ${JSON.stringify(message.staticAnalysis?.lint?.issues)}`, `Type Errors: ${JSON.stringify(message.staticAnalysis?.typecheck?.issues)}`, `Runtime Errors: ${JSON.stringify(message.runtimeErrors)}`, - `Client Errors: ${JSON.stringify(message.clientErrors)}`, ].filter(Boolean).join('\n'); onDebugMessage?.('warning', From d5092b0c1757071efedd9c5a10677691e5497040 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sun, 19 Oct 2025 03:36:26 -0400 Subject: [PATCH 041/150] fix: prevent reuse of aborted controllers and improve sandbox deployment resilience --- worker/agents/core/simpleGeneratorAgent.ts | 16 ++++++++----- .../implementations/BaseAgentService.ts | 23 +++++++++++-------- .../implementations/DeploymentManager.ts | 19 ++++++++++----- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index e6a4acaa..439f7346 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -416,8 +416,8 @@ export class SimpleCodeGeneratorAgent extends Agent { * Reuses existing controller for nested operations (e.g., tool calling) */ protected getOrCreateAbortController(): AbortController { - // Reuse existing controller if present (for nested operations) - if (this.currentAbortController) { + // Don't reuse aborted controllers + if (this.currentAbortController && !this.currentAbortController.signal.aborted) { return this.currentAbortController; } @@ -542,10 +542,10 @@ export class SimpleCodeGeneratorAgent extends Agent { }); let currentDevState = CurrentDevState.PHASE_IMPLEMENTING; const generatedPhases = this.state.generatedPhases; - const completedPhases = generatedPhases.filter(phase => !phase.completed); + const incompletedPhases = generatedPhases.filter(phase => !phase.completed); let phaseConcept : PhaseConceptType | undefined; - if (completedPhases.length > 0) { - phaseConcept = completedPhases[completedPhases.length - 1]; + if (incompletedPhases.length > 0) { + phaseConcept = incompletedPhases[incompletedPhases.length - 1]; } else if (generatedPhases.length > 0) { currentDevState = CurrentDevState.PHASE_GENERATING; } else { @@ -1940,7 +1940,11 @@ export class SimpleCodeGeneratorAgent extends Agent { } async getLogs(_reset?: boolean, durationSeconds?: number): Promise { - const response = await this.getSandboxServiceClient().getLogs(this.state.sandboxInstanceId!, _reset, durationSeconds); + if (!this.state.sandboxInstanceId) { + throw new Error('Cannot get logs: No sandbox instance available'); + } + + const response = await this.getSandboxServiceClient().getLogs(this.state.sandboxInstanceId, _reset, durationSeconds); if (response.success) { return `STDOUT: ${response.logs.stdout}\nSTDERR: ${response.logs.stderr}`; } else { diff --git a/worker/agents/services/implementations/BaseAgentService.ts b/worker/agents/services/implementations/BaseAgentService.ts index a6ea39b1..38de6b7b 100644 --- a/worker/agents/services/implementations/BaseAgentService.ts +++ b/worker/agents/services/implementations/BaseAgentService.ts @@ -54,14 +54,19 @@ export abstract class BaseAgentService { errorMsg: string, onTimeout?: () => void ): Promise { - return Promise.race([ - operation, - new Promise((_, reject) => - setTimeout(() => { - onTimeout?.(); - reject(new Error(errorMsg)); - }, timeoutMs) - ) - ]); + let timeoutId: ReturnType; + + const timeoutPromise = new Promise((_, reject) => { + timeoutId = setTimeout(() => { + onTimeout?.(); + reject(new Error(errorMsg)); + }, timeoutMs); + }); + + try { + return await Promise.race([operation, timeoutPromise]); + } finally { + clearTimeout(timeoutId!); + } } } diff --git a/worker/agents/services/implementations/DeploymentManager.ts b/worker/agents/services/implementations/DeploymentManager.ts index d30bdf8b..29988211 100644 --- a/worker/agents/services/implementations/DeploymentManager.ts +++ b/worker/agents/services/implementations/DeploymentManager.ts @@ -159,8 +159,16 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa const status = await client.getInstanceStatus(instanceId); if (!status.success || !status.isHealthy) { - logger.warn(`Instance ${instanceId} unhealthy, clearing interval`); + logger.warn(`Instance ${instanceId} unhealthy, triggering redeploy`); this.clearHealthCheckInterval(); + + // Trigger redeploy to recover from unhealthy state + try { + await this.deployToSandbox(); + logger.info('Instance redeployed successfully after health check failure'); + } catch (redeployError) { + logger.error('Failed to redeploy after health check failure:', redeployError); + } } } catch (error) { logger.error('Health check failed:', error); @@ -274,7 +282,6 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa } catch (error) { logger.warn('Previous deployment failed, proceeding with new deployment:', error); } - return null; } logger.info("Deploying to sandbox", { files: files.length, redeploy, commitMessage, sessionId: this.getSessionId() }); @@ -338,7 +345,7 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa }); // Start health check after successful deployment - if (result.redeployed) { + if (result.redeployed || this.healthCheckInterval === null) { this.startHealthCheckInterval(result.sandboxInstanceId); } @@ -385,9 +392,9 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa logger.info(`Retrying deployment, ${retries} attempts remaining`); // Exponential backoff - await new Promise(resolve => - setTimeout(resolve, Math.pow(2, MAX_DEPLOYMENT_RETRIES - retries) * 1000) - ); + const attempt = MAX_DEPLOYMENT_RETRIES - retries + 1; + const delayMs = Math.pow(2, attempt - 1) * 1000; + await new Promise(resolve => setTimeout(resolve, delayMs)); return this.executeDeploymentWithRetry( files, From fd638136bc7261869fefbef2692ec488adabc5d9 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sun, 19 Oct 2025 22:36:27 -0400 Subject: [PATCH 042/150] feat: sandboxsdk 0.4.3 port + in-memory template details zip extraction --- SandboxDockerfile | 43 +- bun.lock | 470 +++++++++++++++++- container/types.ts | 34 -- package.json | 4 +- worker/services/sandbox/BaseSandboxService.ts | 92 +++- worker/services/sandbox/fileTreeBuilder.ts | 303 +++++++++++ worker/services/sandbox/sandboxSdkClient.ts | 469 ++++++++--------- worker/services/sandbox/sandboxTypes.ts | 3 +- worker/services/sandbox/zipExtractor.ts | 112 +++++ 9 files changed, 1211 insertions(+), 319 deletions(-) create mode 100644 worker/services/sandbox/fileTreeBuilder.ts create mode 100644 worker/services/sandbox/zipExtractor.ts diff --git a/SandboxDockerfile b/SandboxDockerfile index ccb0d032..10029ae4 100644 --- a/SandboxDockerfile +++ b/SandboxDockerfile @@ -1,8 +1,6 @@ # FROM docker.io/cloudflare/sandbox:0.1.3 -FROM docker.io/cloudflare/sandbox:0.1.3 -# FROM --platform=linux/arm64 docker.io/cloudflare/sandbox:0.1.3 +FROM docker.io/cloudflare/sandbox:0.4.3 -# Install cloudflared and system dependencies in a single layer ARG TARGETARCH RUN apt-get update && \ apt-get install -y git ca-certificates curl procps net-tools && \ @@ -20,37 +18,48 @@ RUN apt-get update && \ git config --global user.name "Cloudflare Vibesdk bot" # Create directory for error monitoring system -RUN mkdir -p /app/container /app/data +RUN mkdir -p /workspace/container /workspace/data # Copy the process monitoring system -COPY container/ /app/container/ +COPY container/ /workspace/container/ + +# Expose the ports you want to expose # Install dependencies for the monitoring system -WORKDIR /app/container +WORKDIR /workspace/container RUN bun install && bun run build # Make scripts executable -RUN chmod +x /app/container/cli-tools.ts +RUN chmod +x /workspace/container/cli-tools.ts + +# Ensure bunx is available (should be included with bun) +RUN which bunx || ln -sf $(which bun) /usr/local/bin/bunx + +# Install tsc via bunx +RUN bunx tsc --version # Create symlinks for easier CLI usage -RUN ln -sf /app/container/cli-tools.ts /usr/local/bin/monitor-cli +RUN ln -sf /workspace/container/cli-tools.ts /usr/local/bin/monitor-cli -# Setup common templates packages cache -WORKDIR /app/container/packages-cache -ENV BUN_INSTALL_CACHE_DIR=/app/container/packages-cache -RUN bun install +# # Setup common templates packages cache +# WORKDIR /app/container/packages-cache +# ENV BUN_INSTALL_CACHE_DIR=/app/container/packages-cache +# RUN bun install # Set proper permissions for data directory -RUN chmod 755 /app/data +RUN chmod 755 /workspace/data # Set environment variable to indicate Docker container environment ENV CONTAINER_ENV=docker ENV VITE_LOGGER_TYPE=json -# Reset workdir -WORKDIR /app +# # Set environment variable to indicate Docker container environment +# ENV CONTAINER_ENV=docker + +# # Reset workdir +WORKDIR /container-server EXPOSE 3000 -# Run the same command as the original image -CMD ["bun", "index.ts"] \ No newline at end of file +# Use startup script +CMD ["./startup.sh"] \ No newline at end of file diff --git a/bun.lock b/bun.lock index 30081e78..bf6c1b8e 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "name": "vibesdk", "dependencies": { "@cloudflare/containers": "^0.0.28", - "@cloudflare/sandbox": "0.1.3", + "@cloudflare/sandbox": "0.4.3", "@noble/ciphers": "^1.3.0", "@octokit/rest": "^22.0.0", "@radix-ui/react-accordion": "^1.2.12", @@ -57,12 +57,14 @@ "esbuild": "^0.25.10", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-import": "^2.32.0", + "fflate": "^0.8.2", "framer-motion": "^12.23.22", "hono": "^4.9.9", "html2canvas-pro": "^1.5.11", "input-otp": "^1.4.2", "jose": "^5.10.0", "jsonc-parser": "^3.3.1", + "latest": "^0.2.0", "lucide-react": "^0.541.0", "monaco-editor": "^0.52.2", "next-themes": "^0.4.6", @@ -220,7 +222,7 @@ "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="], - "@cloudflare/sandbox": ["@cloudflare/sandbox@0.1.3", "", { "dependencies": { "@cloudflare/containers": "^0.0.25" } }, "sha512-j6LB3Ynz2pBlxlwdhaoip72GZmE8TZYRb9yIlAw+pKd4SoKK26OojyXRrTzeBlUqWGuEM8CWp8XMY82MRShRBA=="], + "@cloudflare/sandbox": ["@cloudflare/sandbox@0.4.3", "", { "dependencies": { "@cloudflare/containers": "^0.0.28" } }, "sha512-ODynUVKLID9viGLDDnFpWXNToy4vvN2WCQliJiWvtgBee8K1WiRDI3SYzUHFt3onV8kBHKjkoCbADQHOPBAllA=="], "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.7.5", "", { "peerDependencies": { "unenv": "2.0.0-rc.21", "workerd": "^1.20250924.0" }, "optionalPeers": ["workerd"] }, "sha512-eB3UAIVhrvY+CMZrRXS/bAv5kWdNiH+dgwu+1M1S7keDeonxkfKIGVIrhcCLTkcqYlN30MPURPuVFUEzIWuuvg=="], @@ -1724,6 +1726,8 @@ "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], @@ -2078,6 +2082,8 @@ "knip": ["knip@5.64.1", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "jiti": "^2.6.0", "js-yaml": "^4.1.0", "minimist": "^1.2.8", "oxc-resolver": "^11.8.3", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.4.1", "strip-json-comments": "5.0.2", "zod": "^4.1.11" }, "peerDependencies": { "@types/node": ">=18", "typescript": ">=5.0.4 <7" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-80XnLsyeXuyxj1F4+NBtQFHxaRH0xWRw8EKwfQ6EkVZZ0bSz/kqqan08k/Qg8ajWsFPhFq+0S2RbLCBGIQtuOg=="], + "latest": ["latest@0.2.0", "", { "dependencies": { "npm": "^2.5.1" }, "bin": { "latest": "./bin/latest.js" } }, "sha512-nsIM/FjwLcsKZ1KDAw5CivnM26zzMs3zGBL4SdjYXHI5tMcOWjGhFDMBKIum4WNAkZmeVw7zU1jR2H2UiKoQVA=="], + "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], @@ -2318,6 +2324,8 @@ "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + "npm": ["npm@2.15.12", "", { "dependencies": { "abbrev": "~1.0.9", "ansi": "~0.3.1", "ansi-regex": "*", "ansicolors": "~0.3.2", "ansistyles": "~0.1.3", "archy": "~1.0.0", "async-some": "~1.0.2", "block-stream": "0.0.9", "char-spinner": "~1.0.1", "chmodr": "~1.0.2", "chownr": "~1.0.1", "cmd-shim": "~2.0.2", "columnify": "~1.5.4", "config-chain": "~1.1.10", "dezalgo": "~1.0.3", "editor": "~1.0.0", "fs-vacuum": "~1.2.9", "fs-write-stream-atomic": "~1.0.8", "fstream": "~1.0.10", "fstream-npm": "~1.1.1", "github-url-from-git": "~1.4.0", "github-url-from-username-repo": "~1.0.2", "glob": "~7.0.6", "graceful-fs": "~4.1.6", "hosted-git-info": "~2.1.5", "imurmurhash": "*", "inflight": "~1.0.4", "inherits": "~2.0.3", "ini": "~1.3.4", "init-package-json": "~1.9.4", "lockfile": "~1.0.1", "lru-cache": "~4.0.1", "minimatch": "~3.0.3", "mkdirp": "~0.5.1", "node-gyp": "~3.6.0", "nopt": "~3.0.6", "normalize-git-url": "~3.0.2", "normalize-package-data": "~2.3.5", "npm-cache-filename": "~1.0.2", "npm-install-checks": "~1.0.7", "npm-package-arg": "~4.1.0", "npm-registry-client": "~7.2.1", "npm-user-validate": "~0.1.5", "npmlog": "~2.0.4", "once": "~1.4.0", "opener": "~1.4.1", "osenv": "~0.1.3", "path-is-inside": "~1.0.0", "read": "~1.0.7", "read-installed": "~4.0.3", "read-package-json": "~2.0.4", "readable-stream": "~2.1.5", "realize-package-specifier": "~3.0.1", "request": "~2.74.0", "retry": "~0.10.0", "rimraf": "~2.5.4", "semver": "~5.1.0", "sha": "~2.0.1", "slide": "~1.1.6", "sorted-object": "~2.0.0", "spdx-license-ids": "~1.2.2", "strip-ansi": "~3.0.1", "tar": "~2.2.1", "text-table": "~0.2.0", "uid-number": "0.0.6", "umask": "~1.1.0", "validate-npm-package-license": "~3.0.1", "validate-npm-package-name": "~2.2.2", "which": "~1.2.11", "wrappy": "~1.0.2", "write-file-atomic": "~1.1.4" }, "bin": { "npm": "./bin/npm-cli.js" } }, "sha512-WMoAJ518W0vHjWy1abYnTeyG9YQpSoYGPxAx7d0C0L7U7Jo44bZsrvTjccmDohCJGxpasdKfqsKsl6o/RUPx6A=="], + "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], "obj-multiplex": ["obj-multiplex@1.0.0", "", { "dependencies": { "end-of-stream": "^1.4.0", "once": "^1.4.0", "readable-stream": "^2.3.3" } }, "sha512-0GNJAOsHoBHeNTvl5Vt6IWnpUEcc3uSRxzBri7EDyIcMgYvnY2JL2qdeV5zTMjWQX5OHcD5amcW2HFfDh0gjIA=="], @@ -3324,6 +3332,364 @@ "node-stdlib-browser/pkg-dir": ["pkg-dir@5.0.0", "", { "dependencies": { "find-up": "^5.0.0" } }, "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA=="], + "npm/abbrev": ["abbrev@1.0.9", "", { "bundled": true }, "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q=="], + + "npm/ansi": ["ansi@0.3.1", "", { "bundled": true }, "sha512-iFY7JCgHbepc0b82yLaw4IMortylNb6wG4kL+4R0C3iv6i+RHGHux/yUX5BTiRvSX/shMnngjR1YyNMnXEFh5A=="], + + "npm/ansi-regex": ["ansi-regex@6.2.2", "", { "bundled": true }, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "npm/ansi-styles": ["ansi-styles@2.2.1", "", {}, "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA=="], + + "npm/ansicolors": ["ansicolors@0.3.2", "", { "bundled": true }, "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg=="], + + "npm/ansistyles": ["ansistyles@0.1.3", "", { "bundled": true }, "sha512-6QWEyvMgIXX0eO972y7YPBLSBsq7UWKFAoNNTLGaOJ9bstcEL9sCbcjf96dVfNDdUsRoGOK82vWFJlKApXds7g=="], + + "npm/archy": ["archy@1.0.0", "", { "bundled": true }, "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw=="], + + "npm/are-we-there-yet": ["are-we-there-yet@1.1.7", "", { "dependencies": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" } }, "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g=="], + + "npm/asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="], + + "npm/asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="], + + "npm/assert-plus": ["assert-plus@0.2.0", "", {}, "sha512-u1L0ZLywRziOVjUhRxI0Qg9G+4RnFB9H/Rq40YWn0dieDgO7vAYeJz6jKAO6t/aruzlDFLAPkQTT87e+f8Imaw=="], + + "npm/async": ["async@2.6.4", "", { "dependencies": { "lodash": "^4.17.14" } }, "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA=="], + + "npm/async-some": ["async-some@1.0.2", "", { "dependencies": { "dezalgo": "^1.0.2" }, "bundled": true }, "sha512-VbEsqdl4ztEqzb3Cgpk9L81Q4eyMl3XFdA0i+12Qmyw/bNx4aDAJqmIMKXh1mKGJ2IaPvGvN682YKZDaJMLUdA=="], + + "npm/aws-sign2": ["aws-sign2@0.6.0", "", {}, "sha512-JnJpAS0p9RmixkOvW2XwDxxzs1bd4/VAGIl6Q0EC5YOo+p+hqIhtDhn/nmFnB/xUNXbLkpE2mOjgVIBRKD4xYw=="], + + "npm/aws4": ["aws4@1.13.2", "", {}, "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw=="], + + "npm/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "npm/bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="], + + "npm/bl": ["bl@1.1.2", "", { "dependencies": { "readable-stream": "~2.0.5" } }, "sha512-uVVYHEQk+OuWvCi5U+iquVXvvGCWXKawjwELIR2XMLsqfV/e2sGDClVBs8OlGIgGsStPRY/Es311YKYIlYCWAg=="], + + "npm/block-stream": ["block-stream@0.0.9", "", { "dependencies": { "inherits": "~2.0.0" }, "bundled": true }, "sha512-OorbnJVPII4DuUKbjARAe8u8EfqOmkEEaSFIyoQ7OjTHn6kafxWl0wLgoZ2rXaYd7MyLcDaU4TmhfxtwgcccMQ=="], + + "npm/boom": ["boom@2.10.1", "", { "dependencies": { "hoek": "2.x.x" } }, "sha512-KbiZEa9/vofNcVJXGwdWWn25reQ3V3dHBWbS07FTF3/TOehLnm9GEhJV4T6ZvGPkShRpmUqYwnaCrkj0mRnP6Q=="], + + "npm/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "npm/buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "npm/buffer-shims": ["buffer-shims@1.0.0", "", {}, "sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g=="], + + "npm/builtin-modules": ["builtin-modules@1.1.1", "", {}, "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ=="], + + "npm/builtins": ["builtins@0.0.7", "", {}, "sha512-T8uCGKc0/2aLVt6omt8JxDRBoWEMkku+wFesxnhxnt4NygVZG99zqxo7ciK8eebszceKamGoUiLdkXCgGQyrQw=="], + + "npm/caseless": ["caseless@0.11.0", "", {}, "sha512-ODLXH644w9C2fMPAm7bMDQ3GRvipZWZfKc+8As6hIadRIelE0n0xZuN38NS6kiK3KPEVrpymmQD8bvncAHWQkQ=="], + + "npm/chalk": ["chalk@1.1.3", "", { "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" } }, "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A=="], + + "npm/char-spinner": ["char-spinner@1.0.1", "", { "bundled": true }, "sha512-acv43vqJ0+N0rD+Uw3pDHSxP30FHrywu2NO6/wBaHChJIizpDeBUd6NjqhNhy9LGaEAhZAXn46QzmlAvIWd16g=="], + + "npm/chmodr": ["chmodr@1.0.2", "", { "bundled": true }, "sha512-oHosCxCZpaI/Db320r8M5SHHZuVfnFJVwkSGkI81QLnnVg25pDw//NgD4fy1WvvydhZNi2G+jrbNSprRkfbRYA=="], + + "npm/chownr": ["chownr@1.0.1", "", { "bundled": true }, "sha512-cKnqUJAC8G6cuN1DiRRTifu+s1BlAQNtalzGphFEV0pl0p46dsxJD4l1AOlyKJeLZOFzo3c34R7F3djxaCu8Kw=="], + + "npm/clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], + + "npm/cmd-shim": ["cmd-shim@2.0.2", "", { "dependencies": { "graceful-fs": "^4.1.2", "mkdirp": "~0.5.0" }, "bundled": true }, "sha512-NLt0ntM0kvuSNrToO0RTFiNRHdioWsLW+OgDAEVDvIivsYwR+AjlzvLaMJ2Z+SNRpV3vdsDrHp1WI00eetDYzw=="], + + "npm/columnify": ["columnify@1.5.4", "", { "dependencies": { "strip-ansi": "^3.0.0", "wcwidth": "^1.0.0" }, "bundled": true }, "sha512-rFl+iXVT1nhLQPfGDw+3WcS8rmm7XsLKUmhsGE3ihzzpIikeGrTaZPIRKYWeLsLBypsHzjXIvYEltVUZS84XxQ=="], + + "npm/combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "npm/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "npm/concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "npm/concat-stream": ["concat-stream@1.6.2", "", { "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^2.2.2", "typedarray": "^0.0.6" } }, "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw=="], + + "npm/config-chain": ["config-chain@1.1.13", "", { "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" }, "bundled": true }, "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="], + + "npm/core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + + "npm/cryptiles": ["cryptiles@2.0.5", "", { "dependencies": { "boom": "2.x.x" } }, "sha512-FFN5KwpvvQTTS5hWPxrU8/QE4kQUc6uwZcrnlMBN82t1MgAtq8mnoDwINBly9Tdr02seeIIhtdF+UH1feBYGog=="], + + "npm/dashdash": ["dashdash@1.14.1", "", { "dependencies": { "assert-plus": "^1.0.0" } }, "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g=="], + + "npm/debuglog": ["debuglog@1.0.1", "", {}, "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw=="], + + "npm/defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], + + "npm/delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "npm/delegates": ["delegates@1.0.0", "", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="], + + "npm/dezalgo": ["dezalgo@1.0.4", "", { "dependencies": { "asap": "^2.0.0", "wrappy": "1" }, "bundled": true }, "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig=="], + + "npm/ecc-jsbn": ["ecc-jsbn@0.1.2", "", { "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" } }, "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw=="], + + "npm/editor": ["editor@1.0.0", "", { "bundled": true }, "sha512-SoRmbGStwNYHgKfjOrX2L0mUvp9bUVv0uPppZSOMAntEbcFtoC3MKF5b3T6HQPXKIV+QGY3xPO3JK5it5lVkuw=="], + + "npm/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], + + "npm/extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "npm/extsprintf": ["extsprintf@1.3.0", "", {}, "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g=="], + + "npm/forever-agent": ["forever-agent@0.6.1", "", {}, "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="], + + "npm/form-data": ["form-data@1.0.1", "", { "dependencies": { "async": "^2.0.1", "combined-stream": "^1.0.5", "mime-types": "^2.1.11" } }, "sha512-M4Yhq2mLogpCtpUmfopFlTTuIe6mSCTgKvnlMhDj3NcgVhA1uS20jT0n+xunKPzpmL5w2erSVtp+SKiJf1TlWg=="], + + "npm/fs-vacuum": ["fs-vacuum@1.2.10", "", { "dependencies": { "graceful-fs": "^4.1.2", "path-is-inside": "^1.0.1", "rimraf": "^2.5.2" }, "bundled": true }, "sha512-bwbv1FcWYwxN1F08I1THN8nS4Qe/pGq0gM8dy1J34vpxxp3qgZKJPPaqex36RyZO0sD2J+2ocnbwC2d/OjYICQ=="], + + "npm/fs-write-stream-atomic": ["fs-write-stream-atomic@1.0.10", "", { "dependencies": { "graceful-fs": "^4.1.2", "iferr": "^0.1.5", "imurmurhash": "^0.1.4", "readable-stream": "1 || 2" }, "bundled": true }, "sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA=="], + + "npm/fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + + "npm/fstream": ["fstream@1.0.12", "", { "dependencies": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", "mkdirp": ">=0.5 0", "rimraf": "2" }, "bundled": true }, "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg=="], + + "npm/fstream-ignore": ["fstream-ignore@1.0.5", "", { "dependencies": { "fstream": "^1.0.0", "inherits": "2", "minimatch": "^3.0.0" } }, "sha512-VVRuOs41VUqptEGiR0N5ZoWEcfGvbGRqLINyZAhHRnF3DH5wrqjNkYr3VbRoZnI41BZgO7zIVdiobc13TVI1ow=="], + + "npm/fstream-npm": ["fstream-npm@1.1.1", "", { "dependencies": { "fstream-ignore": "^1.0.0", "inherits": "2" }, "bundled": true }, "sha512-zKZuzxog3e6wPg22PC1UoI+efTOCtngC2b6qAZpUZu3t9oB3A+MuLIUosVPPFVmq6+WZsHZ3h1bhGazc4E9a7Q=="], + + "npm/gauge": ["gauge@1.2.7", "", { "dependencies": { "ansi": "^0.3.0", "has-unicode": "^2.0.0", "lodash.pad": "^4.1.0", "lodash.padend": "^4.1.0", "lodash.padstart": "^4.1.0" } }, "sha512-fVbU2wRE91yDvKUnrIaQlHKAWKY5e08PmztCrwuH5YVQ+Z/p3d0ny2T48o6uvAAXHIUnfaQdHkmxYbQft1eHVA=="], + + "npm/generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], + + "npm/generate-object-property": ["generate-object-property@1.2.0", "", { "dependencies": { "is-property": "^1.0.0" } }, "sha512-TuOwZWgJ2VAMEGJvAyPWvpqxSANF0LDpmyHauMjFYzaACvn+QTT/AZomvPCzVBV7yDN3OmwHQ5OvHaeLKre3JQ=="], + + "npm/getpass": ["getpass@0.1.7", "", { "dependencies": { "assert-plus": "^1.0.0" } }, "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng=="], + + "npm/github-url-from-git": ["github-url-from-git@1.4.0", "", { "bundled": true }, "sha512-Vd1uAwEIbUaaYodSvEZRgMOdLUvY7+kZ+PuJNPVBXldTuVcHFtcLENvl2Ds9KKO9q6Ld2o+eVmA/wabaN3/2mQ=="], + + "npm/github-url-from-username-repo": ["github-url-from-username-repo@1.0.2", "", { "bundled": true }, "sha512-Tj8CQqRoFVTglGdQ8FQmfq8gOOoOYZX7tnOKP8jq8Hdz2OTDhxvtlkLAbrqMYZ7X/YdaYQoUG1IBWxISBfqZ+Q=="], + + "npm/glob": ["glob@7.0.6", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.2", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, "bundled": true }, "sha512-f8c0rE8JiCxpa52kWPAOa3ZaYEnzofDzCQLCn3Vdk0Z5OVLq3BsRFJI4S4ykpeVW6QMGBUkMeUpoEgWnMTnw5Q=="], + + "npm/graceful-fs": ["graceful-fs@4.1.15", "", { "bundled": true }, "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="], + + "npm/har-validator": ["har-validator@2.0.6", "", { "dependencies": { "chalk": "^1.1.1", "commander": "^2.9.0", "is-my-json-valid": "^2.12.4", "pinkie-promise": "^2.0.0" }, "bin": { "har-validator": "bin/har-validator" } }, "sha512-P6tFV+wCcUL3nbyTDAvveDySfbhy0XkDtAIfZP6HITjM2WUsiPna/Eg1Yy93SFXvahqoX+kt0n+6xlXKDXYowA=="], + + "npm/has-ansi": ["has-ansi@2.0.0", "", { "dependencies": { "ansi-regex": "^2.0.0" } }, "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg=="], + + "npm/has-unicode": ["has-unicode@2.0.1", "", {}, "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="], + + "npm/hawk": ["hawk@3.1.3", "", { "dependencies": { "boom": "2.x.x", "cryptiles": "2.x.x", "hoek": "2.x.x", "sntp": "1.x.x" } }, "sha512-X8xbmTc1cbPXcQV4WkLcRMALuyoxhfpFATmyuCxJPOAvrDS4DNnsTAOmKUxMTOWU6TzrTOkxPKwIx5ZOpJVSrg=="], + + "npm/hoek": ["hoek@2.16.3", "", {}, "sha512-V6Yw1rIcYV/4JsnggjBU0l4Kr+EXhpwqXRusENU1Xx6ro00IHPHYNynCuBTOZAPlr3AAmLvchH9I7N/VUdvOwQ=="], + + "npm/hosted-git-info": ["hosted-git-info@2.1.5", "", { "bundled": true }, "sha512-5sLwVGWIA8493A2PzG/py8s+uBrYqrwmLp6C6U5+Gpw5Ll49OOigsuD4LKbUTExbgedTNPvPb/0GV5ohHYYNBg=="], + + "npm/http-signature": ["http-signature@1.1.1", "", { "dependencies": { "assert-plus": "^0.2.0", "jsprim": "^1.2.2", "sshpk": "^1.7.0" } }, "sha512-iUn0NcRULlDGtqNLN1Jxmzayk8ogm7NToldASyZBpM2qggbphjXzNOiw3piN8tgz+e/DRs6X5gAzFwTI6BCRcg=="], + + "npm/iferr": ["iferr@0.1.5", "", {}, "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA=="], + + "npm/imurmurhash": ["imurmurhash@0.1.4", "", { "bundled": true }, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "npm/inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" }, "bundled": true }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + + "npm/inherits": ["inherits@2.0.4", "", { "bundled": true }, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "npm/ini": ["ini@1.3.8", "", { "bundled": true }, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "npm/init-package-json": ["init-package-json@1.9.6", "", { "dependencies": { "glob": "^7.1.1", "npm-package-arg": "^4.0.0 || ^5.0.0", "promzard": "^0.3.0", "read": "~1.0.1", "read-package-json": "1 || 2", "semver": "2.x || 3.x || 4 || 5", "validate-npm-package-license": "^3.0.1", "validate-npm-package-name": "^3.0.0" }, "bundled": true }, "sha512-ocaj90F9qTLDpkhhy+ZemKVexbybm2S0fo65/v13KJDI75kyB7eb8ShhRkxZrbGzzS9833A+o5Bus8aFdQEfOg=="], + + "npm/is-builtin-module": ["is-builtin-module@1.0.0", "", { "dependencies": { "builtin-modules": "^1.0.0" } }, "sha512-C2wz7Juo5pUZTFQVer9c+9b4qw3I5T/CHQxQyhVu7BJel6C22FmsLIWsdseYyOw6xz9Pqy9eJWSkQ7+3iN1HVw=="], + + "npm/is-my-ip-valid": ["is-my-ip-valid@1.0.1", "", {}, "sha512-jxc8cBcOWbNK2i2aTkCZP6i7wkHF1bqKFrwEHuN5Jtg5BSaZHUZQ/JTOJwoV41YvHnOaRyWWh72T/KvfNz9DJg=="], + + "npm/is-my-json-valid": ["is-my-json-valid@2.20.6", "", { "dependencies": { "generate-function": "^2.0.0", "generate-object-property": "^1.1.0", "is-my-ip-valid": "^1.0.0", "jsonpointer": "^5.0.0", "xtend": "^4.0.0" } }, "sha512-1JQwulVNjx8UqkPE/bqDaxtH4PXCe/2VRh/y3p99heOV87HG4Id5/VfDswd+YiAfHcRTfDlWgISycnHuhZq1aw=="], + + "npm/is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="], + + "npm/is-typedarray": ["is-typedarray@1.0.0", "", {}, "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="], + + "npm/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "npm/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "npm/isstream": ["isstream@0.1.2", "", {}, "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="], + + "npm/jsbn": ["jsbn@0.1.1", "", {}, "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="], + + "npm/json-parse-better-errors": ["json-parse-better-errors@1.0.2", "", {}, "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="], + + "npm/json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], + + "npm/json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + + "npm/jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="], + + "npm/jsprim": ["jsprim@1.4.2", "", { "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.4.0", "verror": "1.10.0" } }, "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw=="], + + "npm/lockfile": ["lockfile@1.0.4", "", { "dependencies": { "signal-exit": "^3.0.2" }, "bundled": true }, "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA=="], + + "npm/lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + + "npm/lodash.pad": ["lodash.pad@4.5.1", "", {}, "sha512-mvUHifnLqM+03YNzeTBS1/Gr6JRFjd3rRx88FHWUvamVaT9k2O/kXha3yBSOwB9/DTQrSTLJNHvLBBt2FdX7Mg=="], + + "npm/lodash.padend": ["lodash.padend@4.6.1", "", {}, "sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw=="], + + "npm/lodash.padstart": ["lodash.padstart@4.6.1", "", {}, "sha512-sW73O6S8+Tg66eY56DBk85aQzzUJDtpoXFBgELMd5P/SotAguo+1kYO6RuYgXxA4HJH3LFTFPASX6ET6bjfriw=="], + + "npm/lru-cache": ["lru-cache@4.0.2", "", { "dependencies": { "pseudomap": "^1.0.1", "yallist": "^2.0.0" }, "bundled": true }, "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw=="], + + "npm/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "npm/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "npm/minimatch": ["minimatch@3.0.8", "", { "dependencies": { "brace-expansion": "^1.1.7" }, "bundled": true }, "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q=="], + + "npm/minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "npm/mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bundled": true, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], + + "npm/mute-stream": ["mute-stream@0.0.8", "", {}, "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="], + + "npm/node-gyp": ["node-gyp@3.6.3", "", { "dependencies": { "fstream": "^1.0.0", "glob": "^7.0.3", "graceful-fs": "^4.1.2", "minimatch": "^3.0.2", "mkdirp": "^0.5.0", "nopt": "2 || 3", "npmlog": "0 || 1 || 2 || 3 || 4", "osenv": "0", "request": ">=2.9.0 <2.82.0", "rimraf": "2", "semver": "~5.3.0", "tar": "^2.0.0", "which": "1" }, "bundled": true, "bin": { "node-gyp": "./bin/node-gyp.js" } }, "sha512-7789TDMqJpv5iHxn1cAESCBEC/sBHAFxAvgXAcvzWenEWl0qf6E2Kk/Xwdl5ZclktUJzxJPVa27OMkBvaHKqCQ=="], + + "npm/node-uuid": ["node-uuid@1.4.8", "", { "bin": { "uuid": "./bin/uuid" } }, "sha512-TkCET/3rr9mUuRp+CpO7qfgT++aAxfDRaalQhwPFzI9BY/2rCDn6OfpZOVggi1AXfTPpfkTrg5f5WQx5G1uLxA=="], + + "npm/nopt": ["nopt@3.0.6", "", { "dependencies": { "abbrev": "1" }, "bundled": true, "bin": { "nopt": "./bin/nopt.js" } }, "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg=="], + + "npm/normalize-git-url": ["normalize-git-url@3.0.2", "", { "bundled": true }, "sha512-UEmKT33ssKLLoLCsFJ4Si4fmNQsedNwivXpuNTR4V1I97jU9WZlicTV1xn5QAG5itE5B3Z9zhl8OItP6wIGkRA=="], + + "npm/normalize-package-data": ["normalize-package-data@2.3.8", "", { "dependencies": { "hosted-git-info": "^2.1.4", "is-builtin-module": "^1.0.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" }, "bundled": true }, "sha512-tRXZ2ujyDLIynwO6Dw81AvuedQ6tLgfdD5GCvT4K6yOFgYeanngGfW+eeqd9msmRgUuiZxd5kwrQrb2nsncOZw=="], + + "npm/npm-cache-filename": ["npm-cache-filename@1.0.2", "", { "bundled": true }, "sha512-5v2y1KG06izpGvZJDSBR5q1Ej+NaPDO05yAAWBJE6+3eiId0R176Gz3Qc2vEmJnE+VGul84g6Qpq8fXzD82/JA=="], + + "npm/npm-install-checks": ["npm-install-checks@1.0.7", "", { "dependencies": { "npmlog": "0.1 || 1 || 2", "semver": "^2.3.0 || 3.x || 4 || 5" }, "bundled": true }, "sha512-IyaSpkt3NMXaXezbnHxwEZ6HBWpQFlKBUZ0ZEoUCyq3LoAaZEWJ2mchhJPauuvevhkvi8SNW67FiWZLpUhJ82w=="], + + "npm/npm-package-arg": ["npm-package-arg@4.1.1", "", { "dependencies": { "hosted-git-info": "^2.1.4", "semver": "4 || 5" }, "bundled": true }, "sha512-sKJDnYGLVs+x2ITFUtVB7kk70F4atWShCs55uhrrt2oP1hRMdYLWIzBGUSDSsT4ZASPcdhr8xRQ2r9JY6ouq7Q=="], + + "npm/npm-registry-client": ["npm-registry-client@7.2.1", "", { "dependencies": { "concat-stream": "^1.5.2", "graceful-fs": "^4.1.6", "normalize-package-data": "~1.0.1 || ^2.0.0", "npm-package-arg": "^3.0.0 || ^4.0.0", "once": "^1.3.3", "request": "^2.74.0", "retry": "^0.10.0", "semver": "2 >=2.2.1 || 3.x || 4 || 5", "slide": "^1.1.3" }, "optionalDependencies": { "npmlog": "~2.0.0 || ~3.1.0" }, "bundled": true }, "sha512-igXKTHWr0ipuL+pKNRBV2p2H4xqfnpVtYiWQmTKfq5/eFmZxKh9U+PyoqdKzqyvPTFLNQVqMGy5W5GjOr1Ukeg=="], + + "npm/npm-user-validate": ["npm-user-validate@0.1.5", "", { "bundled": true }, "sha512-86IbFCunHe+6drSd71Aafs8H8xg55lHE9O1/6VS4s+OsBh53xEtQNY1lspkgoaO2b3hhfvDW2FA0eS47inrs1w=="], + + "npm/npmlog": ["npmlog@2.0.4", "", { "dependencies": { "ansi": "~0.3.1", "are-we-there-yet": "~1.1.2", "gauge": "~1.2.5" }, "bundled": true }, "sha512-DaL6RTb8Qh4tMe2ttPT1qWccETy2Vi5/8p+htMpLBeXJTr2CAqnF5WQtSP2eFpvaNbhLZ5uilDb98mRm4Q+lZQ=="], + + "npm/oauth-sign": ["oauth-sign@0.8.2", "", {}, "sha512-VlF07iu3VV3+BTXj43Nmp6Irt/G7j/NgEctUS6IweH1RGhURjjCc2NWtzXFPXXWWfc7hgbXQdtiQu2LGp6MxUg=="], + + "npm/once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" }, "bundled": true }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "npm/opener": ["opener@1.4.3", "", { "bundled": true, "bin": { "opener": "opener.js" } }, "sha512-4Im9TrPJcjAYyGR5gBe3yZnBzw5n3Bfh1ceHHGNOpMurINKc6RdSIPXMyon4BZacJbJc36lLkhipioGbWh5pwg=="], + + "npm/os-homedir": ["os-homedir@1.0.2", "", {}, "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ=="], + + "npm/os-tmpdir": ["os-tmpdir@1.0.2", "", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="], + + "npm/osenv": ["osenv@0.1.5", "", { "dependencies": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" }, "bundled": true }, "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g=="], + + "npm/path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + + "npm/path-is-inside": ["path-is-inside@1.0.2", "", { "bundled": true }, "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w=="], + + "npm/pinkie": ["pinkie@2.0.4", "", {}, "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg=="], + + "npm/pinkie-promise": ["pinkie-promise@2.0.1", "", { "dependencies": { "pinkie": "^2.0.0" } }, "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw=="], + + "npm/process-nextick-args": ["process-nextick-args@1.0.7", "", {}, "sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw=="], + + "npm/promzard": ["promzard@0.3.0", "", { "dependencies": { "read": "1" } }, "sha512-JZeYqd7UAcHCwI+sTOeUDYkvEU+1bQ7iE0UT1MgB/tERkAPkesW46MrpIySzODi+owTjZtiF8Ay5j9m60KmMBw=="], + + "npm/proto-list": ["proto-list@1.2.4", "", {}, "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="], + + "npm/pseudomap": ["pseudomap@1.0.2", "", {}, "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ=="], + + "npm/punycode": ["punycode@1.4.1", "", {}, "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="], + + "npm/qs": ["qs@6.2.4", "", {}, "sha512-E57gmgKXqDda+qWTkUJgIwgJICK7zgMfqZZopTRKZ6mY9gzLlmJN9EpXNnDrTxXFlOM/a+I28kJkF/60rqgnYw=="], + + "npm/read": ["read@1.0.7", "", { "dependencies": { "mute-stream": "~0.0.4" }, "bundled": true }, "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ=="], + + "npm/read-installed": ["read-installed@4.0.3", "", { "dependencies": { "debuglog": "^1.0.1", "read-package-json": "^2.0.0", "readdir-scoped-modules": "^1.0.0", "semver": "2 || 3 || 4 || 5", "slide": "~1.1.3", "util-extend": "^1.0.1" }, "optionalDependencies": { "graceful-fs": "^4.1.2" }, "bundled": true }, "sha512-O03wg/IYuV/VtnK2h/KXEt9VIbMUFbk3ERG0Iu4FhLZw0EP0T9znqrYDGn6ncbEsXUFaUjiVAWXHzxwt3lhRPQ=="], + + "npm/read-package-json": ["read-package-json@2.0.13", "", { "dependencies": { "glob": "^7.1.1", "json-parse-better-errors": "^1.0.1", "normalize-package-data": "^2.0.0", "slash": "^1.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.2" }, "bundled": true }, "sha512-/1dZ7TRZvGrYqE0UAfN6qQb5GYBsNcqS1C0tNK601CFOJmtHI7NIGXwetEPU/OtoFHZL3hDxm4rolFFVE9Bnmg=="], + + "npm/readable-stream": ["readable-stream@2.1.5", "", { "dependencies": { "buffer-shims": "^1.0.0", "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "~1.0.0", "process-nextick-args": "~1.0.6", "string_decoder": "~0.10.x", "util-deprecate": "~1.0.1" }, "bundled": true }, "sha512-NkXT2AER7VKXeXtJNSaWLpWIhmtSE3K2PguaLEeWr4JILghcIKqoLt1A3wHrnpDC5+ekf8gfk1GKWkFXe4odMw=="], + + "npm/readdir-scoped-modules": ["readdir-scoped-modules@1.1.0", "", { "dependencies": { "debuglog": "^1.0.1", "dezalgo": "^1.0.0", "graceful-fs": "^4.1.2", "once": "^1.3.0" } }, "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw=="], + + "npm/realize-package-specifier": ["realize-package-specifier@3.0.3", "", { "dependencies": { "dezalgo": "^1.0.1", "npm-package-arg": "^4.1.1" }, "bundled": true }, "sha512-BwF/SVs7c0BFrh9DI5ovZKu1r95uYPwOcj/LlzKW2rNs+HgivxIN0f/RAtNCl+2++RB/tKI1uz63IMzWD1OqIg=="], + + "npm/request": ["request@2.74.0", "", { "dependencies": { "aws-sign2": "~0.6.0", "aws4": "^1.2.1", "bl": "~1.1.2", "caseless": "~0.11.0", "combined-stream": "~1.0.5", "extend": "~3.0.0", "forever-agent": "~0.6.1", "form-data": "~1.0.0-rc4", "har-validator": "~2.0.6", "hawk": "~3.1.3", "http-signature": "~1.1.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.7", "node-uuid": "~1.4.7", "oauth-sign": "~0.8.1", "qs": "~6.2.0", "stringstream": "~0.0.4", "tough-cookie": "~2.3.0", "tunnel-agent": "~0.4.1" }, "bundled": true }, "sha512-m3uMovC42y63jXe/Sr49/qJdqpSYwQAgYIc487l0zSXI6Z6f5cV/V4a86h2Z+AAwKpt5bfB66KrZxOfOSdh6FQ=="], + + "npm/retry": ["retry@0.10.1", "", { "bundled": true }, "sha512-ZXUSQYTHdl3uS7IuCehYfMzKyIDBNoAuUblvy5oGO5UJSUTmStUUVPXbA9Qxd173Bgre53yCQczQuHgRWAdvJQ=="], + + "npm/rimraf": ["rimraf@2.5.4", "", { "dependencies": { "glob": "^7.0.5" }, "bundled": true, "bin": { "rimraf": "./bin.js" } }, "sha512-Lw7SHMjssciQb/rRz7JyPIy9+bbUshEucPoLRvWqy09vC5zQixl8Uet+Zl+SROBB/JMWHJRdCk1qdxNWHNMvlQ=="], + + "npm/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "npm/safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "npm/semver": ["semver@5.1.1", "", { "bundled": true, "bin": { "semver": "./bin/semver" } }, "sha512-bNx9Zdbi1OUN62PbKeG4IgGG8YILX/nkHJ0NQEBwg5FmX8qTJfqhYd3reqkm0DxHCC8nkazb6UjNiBSHCBWVtA=="], + + "npm/sha": ["sha@2.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "readable-stream": "^2.0.2" }, "bundled": true }, "sha512-Lj/GiNro+/4IIvhDvTo2HDqTmQkbqgg/O3lbkM5lMgagriGPpWamxtq1KJPx7mCvyF1/HG6Hs7zaYaj4xpfXbA=="], + + "npm/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "npm/slash": ["slash@1.0.0", "", {}, "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg=="], + + "npm/slide": ["slide@1.1.6", "", { "bundled": true }, "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw=="], + + "npm/sntp": ["sntp@1.0.9", "", { "dependencies": { "hoek": "2.x.x" } }, "sha512-7bgVOAnPj3XjrKY577S+puCKGCRlUrcrEdsMeRXlg9Ghf5df/xNi6sONUa43WrHUd3TjJBF7O04jYoiY0FVa0A=="], + + "npm/sorted-object": ["sorted-object@2.0.1", "", { "bundled": true }, "sha512-oKAAs26HeTu3qbawzUGCkTOBv/5MRrcuJyRWwbfEnWdpXnXsj+WEM3HTvarV73tMcf9uBEZNZoNDVRL62VLxzA=="], + + "npm/spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="], + + "npm/spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "npm/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + + "npm/spdx-license-ids": ["spdx-license-ids@1.2.2", "", { "bundled": true }, "sha512-qIBFhkh6ILCWNeWEe3ODFPKDYhPJrZpqdNCI2Z+w9lNdH5hoVEkfRLLbRfoIi8fb4xRYmpEOaaMH4G2pwYp/iQ=="], + + "npm/sshpk": ["sshpk@1.18.0", "", { "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", "dashdash": "^1.12.0", "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, "bin": { "sshpk-conv": "bin/sshpk-conv", "sshpk-sign": "bin/sshpk-sign", "sshpk-verify": "bin/sshpk-verify" } }, "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ=="], + + "npm/string_decoder": ["string_decoder@0.10.31", "", {}, "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="], + + "npm/stringstream": ["stringstream@0.0.6", "", {}, "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA=="], + + "npm/strip-ansi": ["strip-ansi@3.0.1", "", { "dependencies": { "ansi-regex": "^2.0.0" }, "bundled": true }, "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg=="], + + "npm/supports-color": ["supports-color@2.0.0", "", {}, "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g=="], + + "npm/tar": ["tar@2.2.2", "", { "dependencies": { "block-stream": "*", "fstream": "^1.0.12", "inherits": "2" }, "bundled": true }, "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA=="], + + "npm/text-table": ["text-table@0.2.0", "", { "bundled": true }, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], + + "npm/tough-cookie": ["tough-cookie@2.3.4", "", { "dependencies": { "punycode": "^1.4.1" } }, "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA=="], + + "npm/tunnel-agent": ["tunnel-agent@0.4.3", "", {}, "sha512-e0IoVDWx8SDHc/hwFTqJDQ7CCDTEeGhmcT9jkWJjoGQSpgBz20nAMr80E3Tpk7PatJ1b37DQDgJR3CNSzcMOZQ=="], + + "npm/tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="], + + "npm/typedarray": ["typedarray@0.0.6", "", {}, "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="], + + "npm/uid-number": ["uid-number@0.0.6", "", { "bundled": true }, "sha512-c461FXIljswCuscZn67xq9PpszkPT6RjheWFQTgCyabJrTUozElanb0YEqv2UGgk247YpcJkFBuSGNvBlpXM9w=="], + + "npm/umask": ["umask@1.1.0", "", { "bundled": true }, "sha512-lE/rxOhmiScJu9L6RTNVgB/zZbF+vGC0/p6D3xnkAePI2o0sMyFG966iR5Ki50OI/0mNi2yaRnxfLsPmEZF/JA=="], + + "npm/util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "npm/util-extend": ["util-extend@1.0.3", "", {}, "sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA=="], + + "npm/validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" }, "bundled": true }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], + + "npm/validate-npm-package-name": ["validate-npm-package-name@2.2.2", "", { "dependencies": { "builtins": "0.0.7" }, "bundled": true }, "sha512-zt38kWHt0j/tv8ZKqZB5lEVT3A41JarczU/ib7L+OXZFAjC2l9kPeujQI1m4smU1nmSwF06MqEetltqVkDmnuQ=="], + + "npm/verror": ["verror@1.10.0", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw=="], + + "npm/wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], + + "npm/which": ["which@1.2.14", "", { "dependencies": { "isexe": "^2.0.0" }, "bundled": true, "bin": { "which": "./bin/which" } }, "sha512-16uPglFkRPzgiUXYMi1Jf8Z5EzN1iB4V0ZtMXcHZnwsBtQhhHeCqoWw7tsUY42hJGNDWtUsVLTjakIa5BgAxCw=="], + + "npm/wrappy": ["wrappy@1.0.2", "", { "bundled": true }, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "npm/write-file-atomic": ["write-file-atomic@1.1.4", "", { "dependencies": { "graceful-fs": "^4.1.2", "imurmurhash": "^0.1.4", "slide": "^1.1.5" }, "bundled": true }, "sha512-c5qespPIeoD/YQTLgdOTe9mcjhK0MhK/URjnIlpuF+4Hoec1flfMRcZY+SWrqGHHRC1oGY1VyNC44wiLQgJMiw=="], + + "npm/xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "npm/yallist": ["yallist@2.1.2", "", {}, "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="], + "obj-multiplex/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], @@ -3640,6 +4006,82 @@ "jest-worker/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "npm/are-we-there-yet/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "npm/bl/readable-stream": ["readable-stream@2.0.6", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "~1.0.0", "process-nextick-args": "~1.0.6", "string_decoder": "~0.10.x", "util-deprecate": "~1.0.1" } }, "sha512-TXcFfb63BQe1+ySzsHZI/5v1aJPCShfqvWJ64ayNImXMsN1Cd0YGk/wm8KB7/OeessgPc9QvS9Zou8QTkFzsLw=="], + + "npm/cmd-shim/graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "npm/concat-stream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "npm/dashdash/assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="], + + "npm/fs-vacuum/graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "npm/fs-write-stream-atomic/graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "npm/fs-write-stream-atomic/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "npm/fstream/graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "npm/fstream/mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + + "npm/fstream-ignore/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "npm/getpass/assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="], + + "npm/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "npm/has-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="], + + "npm/init-package-json/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "npm/init-package-json/semver": ["semver@5.3.0", "", { "bin": { "semver": "./bin/semver" } }, "sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw=="], + + "npm/init-package-json/validate-npm-package-name": ["validate-npm-package-name@3.0.0", "", { "dependencies": { "builtins": "^1.0.3" } }, "sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw=="], + + "npm/jsprim/assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="], + + "npm/node-gyp/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "npm/node-gyp/graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "npm/node-gyp/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "npm/node-gyp/semver": ["semver@5.3.0", "", { "bin": { "semver": "./bin/semver" } }, "sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw=="], + + "npm/npm-install-checks/semver": ["semver@5.3.0", "", { "bin": { "semver": "./bin/semver" } }, "sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw=="], + + "npm/npm-registry-client/graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "npm/read-installed/graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "npm/read-package-json/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "npm/read-package-json/graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "npm/readdir-scoped-modules/graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "npm/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "npm/sha/graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "npm/sha/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "npm/spdx-correct/spdx-license-ids": ["spdx-license-ids@3.0.22", "", {}, "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ=="], + + "npm/spdx-expression-parse/spdx-license-ids": ["spdx-license-ids@3.0.22", "", {}, "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ=="], + + "npm/sshpk/assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="], + + "npm/strip-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="], + + "npm/verror/assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="], + + "npm/verror/core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], + + "npm/write-file-atomic/graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "obj-multiplex/readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], "obj-multiplex/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], @@ -3906,6 +4348,30 @@ "@walletconnect/utils/viem/ox/abitype": ["abitype@1.1.0", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A=="], + "npm/are-we-there-yet/readable-stream/process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "npm/are-we-there-yet/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "npm/concat-stream/readable-stream/process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "npm/concat-stream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "npm/fs-write-stream-atomic/readable-stream/process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "npm/fs-write-stream-atomic/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "npm/init-package-json/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "npm/init-package-json/validate-npm-package-name/builtins": ["builtins@1.0.3", "", {}, "sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ=="], + + "npm/read-package-json/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "npm/rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "npm/sha/readable-stream/process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "npm/sha/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "qrcode/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], diff --git a/container/types.ts b/container/types.ts index f4811284..619dbf26 100644 --- a/container/types.ts +++ b/container/types.ts @@ -328,40 +328,6 @@ export const getLogDbPath = (): string => { return process.env.CLI_LOG_DB_PATH || `${getDataDirectory()}/logs.db`; }; -// CLI tools path resolution for different environments -export const getCliToolsPath = (): string => { - // In Docker container, use absolute path - if (process.env.CONTAINER_ENV === 'docker') { - return '/app/container/cli-tools.ts'; - } - - // For local development, try to find the cli-tools.ts file - const path = require('path'); - const fs = require('fs'); - - // Common locations to check - const possiblePaths = [ - './cli-tools.ts', - './container/cli-tools.ts', - '../container/cli-tools.ts', - path.join(__dirname, 'cli-tools.ts'), - path.join(process.cwd(), 'container/cli-tools.ts') - ]; - - for (const possiblePath of possiblePaths) { - try { - if (fs.existsSync(possiblePath)) { - return path.resolve(possiblePath); - } - } catch (error) { - // Continue checking other paths - } - } - - // Fallback to relative path - return './cli-tools.ts'; -}; - // Legacy constants for backward compatibility export const ERROR_DB_PATH = getErrorDbPath(); export const LOG_DB_PATH = getLogDbPath(); diff --git a/package.json b/package.json index 8d8074e4..95d8468c 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ }, "dependencies": { "@cloudflare/containers": "^0.0.28", - "@cloudflare/sandbox": "0.1.3", + "@cloudflare/sandbox": "0.4.3", "@noble/ciphers": "^1.3.0", "@octokit/rest": "^22.0.0", "@radix-ui/react-accordion": "^1.2.12", @@ -85,12 +85,14 @@ "esbuild": "^0.25.10", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-import": "^2.32.0", + "fflate": "^0.8.2", "framer-motion": "^12.23.22", "hono": "^4.9.9", "html2canvas-pro": "^1.5.11", "input-otp": "^1.4.2", "jose": "^5.10.0", "jsonc-parser": "^3.3.1", + "latest": "^0.2.0", "lucide-react": "^0.541.0", "monaco-editor": "^0.52.2", "next-themes": "^0.4.6", diff --git a/worker/services/sandbox/BaseSandboxService.ts b/worker/services/sandbox/BaseSandboxService.ts index dbe5d70f..aded26e6 100644 --- a/worker/services/sandbox/BaseSandboxService.ts +++ b/worker/services/sandbox/BaseSandboxService.ts @@ -29,11 +29,13 @@ import { ListInstancesResponse, GitHubPushRequest, GitHubPushResponse, - } from './sandboxTypes'; +} from './sandboxTypes'; - import { createObjectLogger, StructuredLogger } from '../../logger'; - import { env } from 'cloudflare:workers' +import { createObjectLogger, StructuredLogger } from '../../logger'; +import { env } from 'cloudflare:workers' import { FileOutputType } from 'worker/agents/schemas'; +import { ZipExtractor } from './zipExtractor'; +import { FileTreeBuilder } from './fileTreeBuilder'; /** * Streaming event for enhanced command execution */ @@ -112,10 +114,88 @@ import { FileOutputType } from 'worker/agents/schemas'; } /** - * Get details for a specific template including files and structure + * Get details for a specific template - fully in-memory, no sandbox operations + * Downloads zip from R2, extracts in memory, and returns all files with metadata * Returns: { success: boolean, templateDetails?: {...}, error?: string } */ - abstract getTemplateDetails(templateName: string): Promise; + static async getTemplateDetails(templateName: string, downloadDir?: string): Promise { + try { + // Download template zip from R2 + const downloadUrl = downloadDir ? `${downloadDir}/${templateName}.zip` : `${templateName}.zip`; + const r2Object = await env.TEMPLATES_BUCKET.get(downloadUrl); + + if (!r2Object) { + throw new Error(`Template '${templateName}' not found in bucket`); + } + + const zipData = await r2Object.arrayBuffer(); + + // Extract all files in memory + const allFiles = ZipExtractor.extractFiles(zipData); + + // Build file tree + const fileTree = FileTreeBuilder.buildFromTemplateFiles(allFiles, { rootPath: '.' }); + + // Extract dependencies from package.json + const packageJsonFile = allFiles.find(f => f.filePath === 'package.json'); + const packageJson = packageJsonFile ? JSON.parse(packageJsonFile.fileContents) : null; + const dependencies = packageJson?.dependencies || {}; + + // Parse metadata files + const dontTouchFile = allFiles.find(f => f.filePath === '.donttouch_files.json'); + const dontTouchFiles = dontTouchFile ? JSON.parse(dontTouchFile.fileContents) : []; + + const redactedFile = allFiles.find(f => f.filePath === '.redacted_files.json'); + const redactedFiles = redactedFile ? JSON.parse(redactedFile.fileContents) : []; + + const importantFile = allFiles.find(f => f.filePath === '.important_files.json'); + const importantFiles = importantFile ? JSON.parse(importantFile.fileContents) : []; + + // Get template info from catalog + const catalogResponse = await BaseSandboxService.listTemplates(); + const catalogInfo = catalogResponse.success + ? catalogResponse.templates.find(t => t.name === templateName) + : null; + + // Remove metadata files and convert to map for efficient lookups + const filteredFiles = allFiles.filter(f => + !f.filePath.startsWith('.') || + (!f.filePath.endsWith('.json') && !f.filePath.startsWith('.git')) + ); + + // Convert array to map: filePath -> fileContents + const filesMap: Record = {}; + for (const file of filteredFiles) { + filesMap[file.filePath] = file.fileContents; + } + + const templateDetails: import('./sandboxTypes').TemplateDetails = { + name: templateName, + description: { + selection: catalogInfo?.description.selection || '', + usage: catalogInfo?.description.usage || '' + }, + fileTree, + allFiles: filesMap, + language: catalogInfo?.language, + deps: dependencies, + importantFiles, + dontTouchFiles, + redactedFiles, + frameworks: catalogInfo?.frameworks || [] + }; + + return { + success: true, + templateDetails + }; + } catch (error) { + return { + success: false, + error: `Failed to get template details: ${error instanceof Error ? error.message : 'Unknown error'}` + }; + } + } // ========================================== // INSTANCE LIFECYCLE (Required) @@ -167,7 +247,7 @@ import { FileOutputType } from 'worker/agents/schemas'; */ abstract getFiles(instanceId: string, filePaths?: string[]): Promise; - abstract getLogs(instanceId: string, onlyRecent?: boolean, durationSeconds?: number): Promise; + abstract getLogs(instanceId: string): Promise; // ========================================== // COMMAND EXECUTION (Required) diff --git a/worker/services/sandbox/fileTreeBuilder.ts b/worker/services/sandbox/fileTreeBuilder.ts new file mode 100644 index 00000000..27d54223 --- /dev/null +++ b/worker/services/sandbox/fileTreeBuilder.ts @@ -0,0 +1,303 @@ +import type { FileTreeNode } from './sandboxTypes'; + +export class FileTreeBuilder { + /** + * Default directories to exclude from file trees + */ + static readonly DEFAULT_EXCLUDED_DIRS = [ + '.github', + 'node_modules', + '.git', + 'dist', + '.wrangler', + '.vscode', + '.next', + '.cache', + '.idea', + '.DS_Store', + 'build', + 'out', + 'coverage' + ]; + + /** + * Default file patterns to exclude from file trees + */ + static readonly DEFAULT_EXCLUDED_FILES = [ + '*.jpg', + '*.jpeg', + '*.png', + '*.gif', + '*.svg', + '*.ico', + '*.webp', + '*.bmp', + '*.pdf', + '*.zip', + '*.tar', + '*.gz' + ]; + + /** + * Build a hierarchical file tree from a flat list of file paths + * @param filePaths - Array of file paths (e.g., ['src/App.tsx', 'package.json']) + * @param options - Optional configuration + * @returns Root node of the file tree + */ + static buildFromPaths( + filePaths: string[], + options?: { + excludeDirs?: string[]; + excludeFiles?: string[]; + rootPath?: string; + } + ): FileTreeNode { + // Input validation + if (!Array.isArray(filePaths)) { + throw new TypeError('filePaths must be an array'); + } + + // Handle empty input + if (filePaths.length === 0) { + return { + path: options?.rootPath || '', + type: 'directory', + children: [] + }; + } + + const excludeDirs = new Set(options?.excludeDirs || this.DEFAULT_EXCLUDED_DIRS); + const excludeFilePatterns = options?.excludeFiles || this.DEFAULT_EXCLUDED_FILES; + const rootPath = options?.rootPath || ''; + + // Convert patterns to regex with proper escaping + const fileExcludeRegexes = excludeFilePatterns.map(pattern => { + const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*'); + return new RegExp('^' + escaped + '$'); + }); + + // Normalize and filter paths based on exclusions + const normalizedPaths = filePaths + .map(path => path.startsWith('./') ? path.substring(2) : path) + .filter(path => path.length > 0); // Remove empty paths + + const filteredPaths = normalizedPaths.filter(normalizedPath => { + const parts = normalizedPath.split('/'); + + // Check if any part is an excluded directory + if (parts.some(part => excludeDirs.has(part))) { + return false; + } + + // Check if filename matches excluded patterns + const filename = parts[parts.length - 1]; + if (filename && fileExcludeRegexes.some(regex => regex.test(filename))) { + return false; + } + + return true; + }); + + // Handle case where all paths were filtered out + if (filteredPaths.length === 0) { + return { + path: rootPath, + type: 'directory', + children: [] + }; + } + + // Track which paths are files (original filtered paths) + const fileSet = new Set(filteredPaths); + + // Collect all unique paths including parent directories + const allPaths = new Set(); + filteredPaths.forEach(filePath => { + const parts = filePath.split('/').filter(p => p.length > 0); + + // Add the file itself + allPaths.add(filePath); + + // Add all parent directories + for (let i = 1; i < parts.length; i++) { + const dirPath = parts.slice(0, i).join('/'); + allPaths.add(dirPath); + } + }); + + // Use the consolidated internal method + return this.buildFromPathsWithTypes(Array.from(allPaths), fileSet, rootPath); + } + + /** + * Build a file tree from TemplateFile objects + * @param files - Array of template files with filePath and fileContents + * @param options - Optional configuration + * @returns Root node of the file tree + */ + static buildFromTemplateFiles( + files: T[], + options?: { + excludeDirs?: string[]; + excludeFiles?: string[]; + rootPath?: string; + } + ): FileTreeNode { + const filePaths = files.map(f => f.filePath); + return this.buildFromPaths(filePaths, options); + } + + /** + * Generate find command exclusions for sandbox execution + * Used when building file trees from sandbox filesystem + */ + static generateFindExclusions(options?: { + excludeDirs?: string[]; + excludeFiles?: string[]; + }): { + dirExclusions: string; + fileExclusions: string; + } { + const excludeDirs = options?.excludeDirs || this.DEFAULT_EXCLUDED_DIRS; + const excludeFiles = options?.excludeFiles || this.DEFAULT_EXCLUDED_FILES; + + const dirExclusions = excludeDirs.map(dir => `-name "${dir}"`).join(' -o '); + const fileExclusions = excludeFiles.map(ext => `-not -name "${ext}"`).join(' '); + + return { + dirExclusions, + fileExclusions + }; + } + + /** + * Parse sandbox find command output and build tree + * Delegates to buildFromPathsWithTypes after parsing + * @param findOutput - Raw output from sandbox find command + * @returns Root node of the file tree, or undefined if parsing fails + */ + static buildFromFindOutput(findOutput: string): FileTreeNode | undefined { + // Validate input + if (!findOutput || typeof findOutput !== 'string') { + console.error('Invalid find output: must be a non-empty string'); + return undefined; + } + + try { + const sections = findOutput.split('===DIRS==='); + if (sections.length < 2) { + console.error('Invalid find output format: missing ===DIRS=== separator'); + return undefined; + } + + const fileSection = sections[0].replace('===FILES===', '').trim(); + const dirSection = sections[1].trim(); + + const files = fileSection + .split('\n') + .filter(line => line.trim().length > 0 && line.trim() !== '.') + .map(f => f.startsWith('./') ? f.substring(2) : f) + .filter(f => f.length > 0); + + const dirs = dirSection + .split('\n') + .filter(line => line.trim().length > 0 && line.trim() !== '.') + .map(d => d.startsWith('./') ? d.substring(2) : d) + .filter(d => d.length > 0); + + // Combine all paths (files are explicitly marked) + const allPaths = [...files, ...dirs].filter(path => path.length > 0); + + // Handle empty output + if (allPaths.length === 0) { + return { + path: '', + type: 'directory', + children: [] + }; + } + + // Use the consolidated internal method with explicit file marking + return this.buildFromPathsWithTypes(allPaths, new Set(files), ''); + } catch (error) { + console.error('Failed to parse find output:', error instanceof Error ? error.message : error); + return undefined; + } + } + + /** + * Internal method: Build tree from paths with explicit file/dir marking + * This is the single source of truth for tree construction + * @param paths - All paths (files and directories) + * @param filePaths - Set of paths that are files (not directories) + * @param rootPath - Path for the root node (default: '') + * @returns Root node of the file tree + */ + private static buildFromPathsWithTypes( + paths: string[], + filePaths: Set, + rootPath: string = '' + ): FileTreeNode { + // Create root node + const root: FileTreeNode = { + path: rootPath, + type: 'directory', + children: [] + }; + + // Handle empty paths + if (paths.length === 0) { + return root; + } + + // Sort paths for consistent tree building (copy to avoid mutation) + const sortedPaths = [...paths].sort(); + + // Build tree structure + sortedPaths.forEach(filePath => { + // Skip empty paths + if (!filePath) { + return; + } + + const parts = filePath.split('/').filter(part => part.length > 0); + + // Skip if no valid parts + if (parts.length === 0) { + return; + } + + let current = root; + + parts.forEach((_, index) => { + const pathSoFar = parts.slice(0, index + 1).join('/'); + const isFile = filePaths.has(pathSoFar); + + // Find existing child or create new one + let child = current.children?.find(c => c.path === pathSoFar); + + if (!child) { + child = { + path: pathSoFar, + type: isFile ? 'file' : 'directory', + children: isFile ? undefined : [] + }; + + // Ensure children array exists + if (!current.children) { + current.children = []; + } + + current.children.push(child); + } + + // Navigate to child if it's a directory + if (!isFile && child.children) { + current = child; + } + }); + }); + + return root; + } +} diff --git a/worker/services/sandbox/sandboxSdkClient.ts b/worker/services/sandbox/sandboxSdkClient.ts index fd0e4e5d..4922ae8c 100644 --- a/worker/services/sandbox/sandboxSdkClient.ts +++ b/worker/services/sandbox/sandboxSdkClient.ts @@ -1,4 +1,4 @@ -import { getSandbox, Sandbox, ExecuteResponse, parseSSEStream, LogEvent } from '@cloudflare/sandbox'; +import { getSandbox, Sandbox, parseSSEStream, LogEvent } from '@cloudflare/sandbox'; import { TemplateDetailsResponse, @@ -20,8 +20,6 @@ import { CodeIssue, InstanceDetails, LintSeverity, - TemplateInfo, - TemplateDetails, GitHubPushRequest, GitHubPushResponse, GetLogsResponse, ListInstancesResponse, @@ -50,6 +48,7 @@ import { GitHubService } from '../github/GitHubService'; import { getPreviewDomain } from '../../utils/urls'; import { isDev } from 'worker/utils/envs'; import { FileOutputType } from 'worker/agents/schemas'; +import { FileTreeBuilder } from './fileTreeBuilder'; // Export the Sandbox class in your Worker export { Sandbox as UserAppSandboxService, Sandbox as DeployerService} from "@cloudflare/sandbox"; @@ -108,6 +107,8 @@ function getAutoAllocatedSandbox(sessionId: string): string { export class SandboxSdkClient extends BaseSandboxService { private sandbox: SandboxType; private metadataCache = new Map(); + private sessionCache = new Map>>(); + private defaultSession: Awaited> | null = null; constructor(sandboxId: string, agentId: string) { if (env.ALLOCATION_STRATEGY === AllocationStrategy.MANY_TO_ONE) { @@ -125,12 +126,15 @@ export class SandboxSdkClient extends BaseSandboxService { } async initialize(): Promise { + // Initialize default session for sandbox operations + await this.getDefaultSession(); + // Run a echo command to check if the sandbox is working - const echoResult = await this.sandbox.exec('echo "Hello World"'); + const echoResult = await this.safeSandboxExec('echo "Hello World"'); if (echoResult.exitCode !== 0) { throw new Error(`Failed to run echo command: ${echoResult.stderr}`); } - this.logger.info('Sandbox initialization complete') + this.logger.info('Sandbox initialization complete'); } private getWranglerKVKey(instanceId: string): string { @@ -144,20 +148,99 @@ export class SandboxSdkClient extends BaseSandboxService { return this.sandbox; } + /** + * Generic session getter with caching and automatic recovery + * Properly handles existing sessions and ensures correct cwd + */ + private async getOrCreateSession(sessionId: string, cwd: string): Promise>> { + try { + // Try to create a new session with the specified cwd + this.logger.info('Creating new session', { sessionId, cwd }); + const session = await this.getSandbox().createSession({ id: sessionId, cwd }); + return session; + } catch (error) { + // If session already exists, get it + this.logger.info('Session already exists, retrieving it', { sessionId, cwd }); + const existingSession = await this.getSandbox().getSession(sessionId); + + // Verify the cwd matches what we expect + const pwdResult = await existingSession.exec('pwd'); + const actualCwd = pwdResult.stdout.trim(); + + if (actualCwd !== cwd) { + this.logger.warn('Existing session has wrong cwd, attempting to change directory', { + sessionId, + expectedCwd: cwd, + actualCwd + }); + // Try to cd to the correct directory + await existingSession.exec(`cd ${cwd}`); + const verifyResult = await existingSession.exec('pwd'); + if (verifyResult.stdout.trim() !== cwd) { + throw new Error(`Failed to set working directory to ${cwd}, currently at ${verifyResult.stdout.trim()}`); + } + this.logger.info('Successfully changed directory for existing session', { sessionId, cwd }); + } + + return existingSession; + } + } + + /** + * Get or create default session for anonymous sandbox operations + */ + private async getDefaultSession() { + if (!this.defaultSession) { + this.defaultSession = await this.getOrCreateSession('sandbox-default', '/workspace'); + } + return this.defaultSession; + } + + /** + * Safe wrapper for direct sandbox exec calls using default session + */ + private async safeSandboxExec(command: string, options?: {timeout?: number}): Promise>> { + const session = await this.getDefaultSession(); + return await session.exec(command, options); + } + + /** + * Get or create a session for an instance with automatic caching. + * Environment variables should be set via .dev.vars file. + */ + private async getInstanceSession(instanceId: string) { + if (!this.sessionCache.has(instanceId)) { + const cwd = `/workspace/${instanceId}`; + const session = await this.getOrCreateSession(instanceId, cwd); + this.sessionCache.set(instanceId, session); + } + const session = this.sessionCache.get(instanceId); + return session!; + } + + /** + * Invalidate session cache (call when instance is destroyed) + */ + private invalidateSessionCache(instanceId: string): void { + if (this.sessionCache.has(instanceId)) { + this.sessionCache.delete(instanceId); + this.logger.debug('Session cache invalidated', { instanceId }); + } + } + /** Write a binary file to the sandbox using small base64 chunks to avoid large control messages. */ private async writeBinaryFileViaBase64(targetPath: string, data: ArrayBuffer, bytesPerChunk: number = 16 * 1024): Promise { - const sandbox = this.getSandbox(); const dir = targetPath.includes('/') ? targetPath.slice(0, targetPath.lastIndexOf('/')) : '.'; // Ensure directory and clean target file - await sandbox.exec(`mkdir -p '${dir}'`); - await sandbox.exec(`rm -f '${targetPath}'`); + await this.safeSandboxExec(`mkdir -p '${dir}'`); + await this.safeSandboxExec(`rm -f '${targetPath}'`); const buffer = new Uint8Array(data); for (let i = 0; i < buffer.length; i += bytesPerChunk) { const chunk = buffer.subarray(i, Math.min(i + bytesPerChunk, buffer.length)); const base64Chunk = btoa(String.fromCharCode(...chunk)); // Append decoded bytes into the target file inside the sandbox - const appendResult = await sandbox.exec(`printf '%s' '${base64Chunk}' | base64 -d >> '${targetPath}'`); + const appendResult = await this.safeSandboxExec(`printf '%s' '${base64Chunk}' | base64 -d >> '${targetPath}'`); if (appendResult.exitCode !== 0) { throw new Error(`Failed to append to ${targetPath}: ${appendResult.stderr}`); } @@ -185,9 +268,9 @@ export class SandboxSdkClient extends BaseSandboxService { return `${instanceId}-metadata.json`; } - private async executeCommand(instanceId: string, command: string, timeout?: number): Promise { - return await this.getSandbox().exec(`cd ${instanceId} && ${command}`, { timeout }); - // return await this.getSandbox().exec(command, { cwd: instanceId, timeout }); + private async executeCommand(instanceId: string, command: string, timeout?: number): Promise>['exec']>>> { + const session = await this.getInstanceSession(instanceId); + return await session.exec(command, { timeout }); } private async getInstanceMetadata(instanceId: string): Promise { @@ -198,7 +281,11 @@ export class SandboxSdkClient extends BaseSandboxService { // Cache miss - read from disk try { - const metadataFile = await this.getSandbox().readFile(this.getInstanceMetadataFile(instanceId)); + const session = await this.getDefaultSession(); + const metadataFile = await session.readFile(this.getInstanceMetadataFile(instanceId)); + if (!metadataFile.success) { + throw new Error('Failed to read instance metadata file'); + } const metadata = JSON.parse(metadataFile.content) as InstanceMetadata; this.metadataCache.set(instanceId, metadata); // Cache it return metadata; @@ -209,7 +296,11 @@ export class SandboxSdkClient extends BaseSandboxService { } private async storeInstanceMetadata(instanceId: string, metadata: InstanceMetadata): Promise { - await this.getSandbox().writeFile(this.getInstanceMetadataFile(instanceId), JSON.stringify(metadata)); + const session = await this.getDefaultSession(); + const result = await session.writeFile(this.getInstanceMetadataFile(instanceId), JSON.stringify(metadata)); + if (!result.success) { + throw new Error(`Failed to write instance metadata: ${result.path}`); + } this.metadataCache.set(instanceId, metadata); // Update cache } @@ -228,18 +319,19 @@ export class SandboxSdkClient extends BaseSandboxService { ! netstat -tuln 2>/dev/null | grep -q ":$port " && ! ss -tuln 2>/dev/null | grep -q ":$port "; then echo $port - exit 0 + break fi done - exit 1 `; - const result = await this.getSandbox().exec(findPortCmd.trim()); + const result = await this.safeSandboxExec(findPortCmd.trim()); const endTime = Date.now(); const duration = (endTime - startTime) / 1000; this.logger.info(`Port allocation took ${duration} seconds`); - if (result.exitCode === 0 && result.stdout.trim()) { - const port = parseInt(result.stdout.trim()); + + const portStr = result.stdout.trim(); + if (portStr) { + const port = parseInt(portStr); this.logger.info(`Allocated available port: ${port}`); return port; } @@ -249,8 +341,7 @@ export class SandboxSdkClient extends BaseSandboxService { private async checkTemplateExists(templateName: string): Promise { // Single command to check if template directory and package.json both exist - const sandbox = this.getSandbox(); - const checkResult = await sandbox.exec(`test -f ${templateName}/package.json && echo "exists" || echo "missing"`); + const checkResult = await this.safeSandboxExec(`test -f ${templateName}/package.json && echo "exists" || echo "missing"`); return checkResult.exitCode === 0 && checkResult.stdout.trim() === "exists"; } @@ -280,7 +371,7 @@ export class SandboxSdkClient extends BaseSandboxService { await this.writeBinaryFileViaBase64(`${templateName}.zip`, zipData); this.logger.info(`Wrote zip file to sandbox in chunks: ${templateName}.zip`); - const setupResult = await this.getSandbox().exec(`unzip -o -q ${templateName}.zip -d ${isInstance ? '.' : templateName}`); + const setupResult = await this.safeSandboxExec(`unzip -o -q ${templateName}.zip -d ${isInstance ? '.' : templateName}`); if (setupResult.exitCode !== 0) { throw new Error(`Failed to download/extract template: ${setupResult.stderr}`); @@ -290,174 +381,30 @@ export class SandboxSdkClient extends BaseSandboxService { } } - async getTemplateDetails(templateName: string): Promise { - try { - this.logger.info('Retrieving template details', { templateName }); - - await this.ensureTemplateExists(templateName); - this.logger.info('Template setup complete'); - - const [fileTree, catalogInfo, dontTouchFiles, redactedFiles] = await Promise.all([ - this.buildFileTree(templateName), - this.getTemplateFromCatalog(templateName), - this.fetchDontTouchFiles(templateName), - this.fetchRedactedFiles(templateName) - ]); - - if (!fileTree) { - throw new Error(`Failed to build file tree for template ${templateName}`); - } - - const filesResponse = await this.getFiles(templateName, undefined, true, redactedFiles); // Use template name as directory - - this.logger.info('Template files retrieved'); - - // Parse package.json for dependencies - let dependencies: Record = {}; - try { - const packageJsonFile = filesResponse.files.find(file => file.filePath === 'package.json'); - if (!packageJsonFile) { - throw new Error('package.json not found'); - } - const packageJson = JSON.parse(packageJsonFile.fileContents) as { - dependencies?: Record; - devDependencies?: Record; - }; - dependencies = { - ...packageJson.dependencies || {}, - ...packageJson.devDependencies || {} - }; - } catch { - this.logger.info('No package.json found', { templateName }); - } - const templateDetails: TemplateDetails = { - name: templateName, - description: { - selection: catalogInfo?.description.selection || '', - usage: catalogInfo?.description.usage || '' - }, - fileTree, - files: filesResponse.files, - language: catalogInfo?.language, - deps: dependencies, - dontTouchFiles, - redactedFiles, - frameworks: catalogInfo?.frameworks || [] - }; - - this.logger.info('Template files retrieved', { templateName, fileCount: filesResponse.files.length }); - - return { - success: true, - templateDetails - }; - } catch (error) { - this.logger.error('getTemplateDetails', error, { templateName }); - return { - success: false, - error: `Failed to get template details: ${error instanceof Error ? error.message : 'Unknown error'}` - }; - } - } - - private async getTemplateFromCatalog(templateName: string): Promise { - try { - const templatesResponse = await SandboxSdkClient.listTemplates(); - if (templatesResponse.success) { - return templatesResponse.templates.find(t => t.name === templateName) || null; - } - return null; - } catch { - return null; - } + async getTemplateDetails(templateName: string): Promise { + // Delegate to static method - no sandbox operations needed + this.logger.info('Retrieving template details', { templateName }); + const result = await BaseSandboxService.getTemplateDetails(templateName); + this.logger.info('Template details retrieved', { + templateName, + success: result.success, + fileCount: result.templateDetails?.allFiles ? Object.keys(result.templateDetails.allFiles).length : 0 + }); + return result; } private async buildFileTree(instanceId: string): Promise { try { - // Directories to exclude from file tree - const EXCLUDED_DIRS = [ - ".github", - "node_modules", - ".git", - "dist", - ".wrangler", - ".vscode", - ".next", - ".cache", - ".idea", - ".DS_Store" - ]; - // Build exclusion string for find command - const excludedDirsFind = EXCLUDED_DIRS.map(dir => `-name "${dir}"`).join(" -o "); - // File type exclusions - const excludedFileTypes = [ - "*.jpg", - "*.jpeg", - "*.png", - "*.gif", - "*.svg", - "*.ico", - "*.webp", - "*.bmp" - ]; - const excludedFilesFind = excludedFileTypes.map(ext => `-not -name "${ext}"`).join(" "); + // Generate find command with exclusions + const { dirExclusions, fileExclusions } = FileTreeBuilder.generateFindExclusions(); + // Build the command dynamically - const buildTreeCmd = `echo "===FILES==="; find . -type d \\( ${excludedDirsFind} \\) -prune -o \\( -type f ${excludedFilesFind} \\) -print; echo "===DIRS==="; find . -type d \\( ${excludedDirsFind} \\) -prune -o -type d -print`; + const buildTreeCmd = `echo "===FILES==="; find . -type d \\( ${dirExclusions} \\) -prune -o \\( -type f ${fileExclusions} \\) -print; echo "===DIRS==="; find . -type d \\( ${dirExclusions} \\) -prune -o -type d -print`; const filesResult = await this.executeCommand(instanceId, buildTreeCmd); if (filesResult.exitCode === 0) { - const output = filesResult.stdout.trim(); - const sections = output.split('===DIRS==='); - const fileSection = sections[0].replace('===FILES===', '').trim(); - const dirSection = sections[1] ? sections[1].trim() : ''; - - const files = fileSection.split('\n').filter(line => line.trim() && line !== '.'); - const dirs = dirSection.split('\n').filter(line => line.trim() && line !== '.'); - - // Create sets for quick lookup - const fileSet = new Set(files.map(f => f.startsWith('./') ? f.substring(2) : f)); - // const dirSet = new Set(dirs.map(d => d.startsWith('./') ? d.substring(2) : d)); - - // Combine all paths - const allPaths = [...files, ...dirs].map(path => - path.startsWith('./') ? path.substring(2) : path - ).filter(path => path && path !== '.'); - - // Build tree with proper file/directory detection - const root: FileTreeNode = { - path: '', - type: 'directory', - children: [] - }; - - allPaths.forEach(filePath => { - const parts = filePath.split('/').filter(part => part); - let current = root; - - parts.forEach((_, index) => { - const path = parts.slice(0, index + 1).join('/'); - const isFile = fileSet.has(path); - - let child = current.children?.find(c => c.path === path); - - if (!child) { - child = { - path, - type: isFile ? 'file' : 'directory', - children: isFile ? undefined : [] - }; - current.children = current.children || []; - current.children.push(child); - } - - if (!isFile) { - current = child; - } - }); - }); - - return root; + return FileTreeBuilder.buildFromFindOutput(filesResult.stdout.trim()); } } catch (error) { this.logger.warn('Failed to build file tree', error); @@ -473,10 +420,8 @@ export class SandboxSdkClient extends BaseSandboxService { try { this.logger.info('Retrieving instance metadata'); - const sandbox = this.getSandbox(); - // Use a single command to find metadata files only in current directory (not nested) - const bulkResult = await sandbox.exec(`find . -maxdepth 1 -name "*-metadata.json" -type f -exec sh -c 'echo "===FILE:$1==="; cat "$1"' _ {} \\;`); + const bulkResult = await this.safeSandboxExec(`find . -maxdepth 1 -name "*-metadata.json" -type f -exec sh -c 'echo "===FILE:$1==="; cat "$1"' _ {} \\;`); if (bulkResult.exitCode !== 0) { return { @@ -489,9 +434,9 @@ export class SandboxSdkClient extends BaseSandboxService { const instances: InstanceDetails[] = []; // Parse the combined output - const sections = bulkResult.stdout.split('===FILE:').filter(section => section.trim()); + const metadataSections: string[] = bulkResult.stdout.split('===FILE:').filter((section: string) => section.trim()); - for (const section of sections) { + for (const section of metadataSections) { try { const lines = section.trim().split('\n'); if (lines.length < 2) continue; @@ -608,10 +553,13 @@ export class SandboxSdkClient extends BaseSandboxService { private async startDevServer(instanceId: string, port: number): Promise { try { - // Use CLI tools for enhanced monitoring instead of direct process start - const process = await this.getSandbox().startProcess( - `VITE_LOGGER_TYPE=json monitor-cli process start --instance-id ${instanceId} --port ${port} -- bun run dev`, - { cwd: instanceId } + // Use session-based process management + // Note: Environment variables should already be set via setLocalEnvVars + const session = await this.getOrCreateSession(`${instanceId}-dev`, `/workspace/${instanceId}`); + + // Start process with env vars inline for those not in .dev.vars + const process = await session.startProcess( + `VITE_LOGGER_TYPE=json PORT=${port} monitor-cli process start --instance-id ${instanceId} --port ${port} -- bun run dev` ); this.logger.info('Development server started', { instanceId, processId: process.id }); @@ -640,10 +588,10 @@ export class SandboxSdkClient extends BaseSandboxService { */ private async provisionTemplateResources(instanceId: string, projectName: string): Promise { try { - const sandbox = this.getSandbox(); + const session = await this.getInstanceSession(instanceId); - // Read wrangler.jsonc file - const wranglerFile = await sandbox.readFile(`${instanceId}/wrangler.jsonc`); + // Read wrangler.jsonc file using absolute path + const wranglerFile = await session.readFile(`/workspace/${instanceId}/wrangler.jsonc`); if (!wranglerFile.success) { this.logger.info(`No wrangler.jsonc found for ${instanceId}, skipping resource provisioning`); return { @@ -728,7 +676,7 @@ export class SandboxSdkClient extends BaseSandboxService { let wranglerUpdated = false; if (Object.keys(replacements).length > 0) { const updatedContent = templateParser.replacePlaceholders(wranglerFile.content, replacements); - const writeResult = await sandbox.writeFile(`${instanceId}/wrangler.jsonc`, updatedContent); + const writeResult = await session.writeFile(`/workspace/${instanceId}/wrangler.jsonc`, updatedContent); if (writeResult.success) { wranglerUpdated = true; @@ -772,9 +720,9 @@ export class SandboxSdkClient extends BaseSandboxService { */ private async startCloudflaredTunnel(instanceId: string, port: number): Promise { try { - const process = await this.getSandbox().startProcess( - `cloudflared tunnel --url http://localhost:${port}`, - { cwd: instanceId } + const session = await this.getOrCreateSession(`${instanceId}-tunnel`, `/workspace/${instanceId}`); + const process = await session.startProcess( + `cloudflared tunnel --url http://localhost:${port}` ); this.logger.info(`Started cloudflared tunnel for ${instanceId}`); @@ -827,11 +775,11 @@ export class SandboxSdkClient extends BaseSandboxService { */ private async updateProjectConfiguration(instanceId: string, projectName: string): Promise { try { - const sandbox = this.getSandbox(); + const session = await this.getInstanceSession(instanceId); // Update package.json with new project name (top-level only) this.logger.info(`Updating package.json with project name: ${projectName}`); - const packageJsonResult = await sandbox.exec(`cd ${instanceId} && sed -i '1,10s/^[ \t]*"name"[ ]*:[ ]*"[^"]*"/ "name": "${projectName}"/' package.json`); + const packageJsonResult = await session.exec(`sed -i '1,10s/^[ \t]*"name"[ ]*:[ ]*"[^"]*"/ "name": "${projectName}"/' package.json`); if (packageJsonResult.exitCode !== 0) { this.logger.warn('Failed to update package.json', packageJsonResult.stderr); @@ -839,7 +787,7 @@ export class SandboxSdkClient extends BaseSandboxService { // Update wrangler.jsonc with new project name (top-level only) this.logger.info(`Updating wrangler.jsonc with project name: ${projectName}`); - const wranglerResult = await sandbox.exec(`cd ${instanceId} && sed -i '0,/"name":/s/"name"[ ]*:[ ]*"[^"]*"/"name": "${projectName}"/' wrangler.jsonc`); + const wranglerResult = await session.exec(`sed -i '0,/"name":/s/"name"[ ]*:[ ]*"[^"]*"/"name": "${projectName}"/' wrangler.jsonc`); if (wranglerResult.exitCode !== 0) { this.logger.warn('Failed to update wrangler.jsonc', wranglerResult.stderr); @@ -854,12 +802,16 @@ export class SandboxSdkClient extends BaseSandboxService { private async setLocalEnvVars(instanceId: string, localEnvVars: Record): Promise { try { - const sandbox = this.getSandbox(); - // Simply save all env vars in '.dev.vars' file + // Write .dev.vars file - tools will read environment variables from this file + const session = await this.getInstanceSession(instanceId); const envVarsContent = Object.entries(localEnvVars) .map(([key, value]) => `${key}=${value}`) .join('\n'); - await sandbox.writeFile(`${instanceId}/.dev.vars`, envVarsContent); + const result = await session.writeFile(`/workspace/${instanceId}/.dev.vars`, envVarsContent); + if (!result.success) { + throw new Error('Failed to write .dev.vars file'); + } + this.logger.info('Environment variables written to .dev.vars', { instanceId, varCount: Object.keys(localEnvVars).length }); } catch (error) { this.logger.error(`Error setting local environment variables: ${error}`); throw error; @@ -880,7 +832,8 @@ export class SandboxSdkClient extends BaseSandboxService { // Store wrangler.jsonc configuration in KV after resource provisioning try { - const wranglerConfigFile = await sandbox.readFile(`${instanceId}/wrangler.jsonc`); + const session = await this.getInstanceSession(instanceId); + const wranglerConfigFile = await session.readFile(`/workspace/${instanceId}/wrangler.jsonc`); if (wranglerConfigFile.success) { await env.VibecoderStore.put(this.getWranglerKVKey(instanceId), wranglerConfigFile.content); this.logger.info('Wrangler configuration stored in KV', { instanceId }); @@ -958,10 +911,12 @@ export class SandboxSdkClient extends BaseSandboxService { private async fetchDontTouchFiles(templateName: string): Promise { let donttouchFiles: string[] = []; try { - // Read .donttouch_files.json - const donttouchFile = await this.getSandbox().readFile(`${templateName}/.donttouch_files.json`); - if (donttouchFile.exitCode !== 0) { - this.logger.warn(`Failed to read .donttouch_files.json: ${donttouchFile.content}`); + // Read .donttouch_files.json using default session with full path + const session = await this.getDefaultSession(); + const donttouchFile = await session.readFile(`${templateName}/.donttouch_files.json`); + if (!donttouchFile.success) { + this.logger.warn('Failed to read .donttouch_files.json'); + return donttouchFiles; } donttouchFiles = JSON.parse(donttouchFile.content) as string[]; } catch (error) { @@ -973,10 +928,12 @@ export class SandboxSdkClient extends BaseSandboxService { private async fetchRedactedFiles(templateName: string): Promise { let redactedFiles: string[] = []; try { - // Read .redacted_files.json - const redactedFile = await this.getSandbox().readFile(`${templateName}/.redacted_files.json`); - if (redactedFile.exitCode !== 0) { - this.logger.warn(`Failed to read .redacted_files.json: ${redactedFile.content}`); + // Read .redacted_files.json using default session with full path + const session = await this.getDefaultSession(); + const redactedFile = await session.readFile(`${templateName}/.redacted_files.json`); + if (!redactedFile.success) { + this.logger.warn('Failed to read .redacted_files.json'); + return redactedFiles; } redactedFiles = JSON.parse(redactedFile.content) as string[]; } catch (error) { @@ -987,11 +944,9 @@ export class SandboxSdkClient extends BaseSandboxService { async createInstance(templateName: string, projectName: string, webhookUrl?: string, localEnvVars?: Record): Promise { try { - const sandbox = this.getSandbox(); - // Set environment variables FIRST, before any other operations + // Environment variables will be set via session creation on first use if (localEnvVars && Object.keys(localEnvVars).length > 0) { - this.logger.info('Configuring environment variables', { envVars: Object.keys(localEnvVars) }); - sandbox.setEnvVars(localEnvVars); + this.logger.info('Environment variables will be configured via session', { envVars: Object.keys(localEnvVars) }); } if (env.ALLOCATION_STRATEGY === 'one_to_one') { // Multiple instances shouldn't exist in the same sandbox @@ -1032,7 +987,7 @@ export class SandboxSdkClient extends BaseSandboxService { this.fetchRedactedFiles(templateName) ]); - const moveTemplateResult = await sandbox.exec(`mv ${templateName} ${instanceId}`); + const moveTemplateResult = await this.safeSandboxExec(`mv ${templateName} ${instanceId}`); if (moveTemplateResult.exitCode !== 0) { throw new Error(`Failed to move template: ${moveTemplateResult.stderr}`); } @@ -1144,11 +1099,12 @@ export class SandboxSdkClient extends BaseSandboxService { if (metadata.processId) { for (let i = 0; i < 3; i++) { try { - const process = await this.getSandbox().getProcess(metadata.processId); + const processes = await this.getSandbox().listProcesses(); + const process = processes.find((p: {id: string; status: string}) => p.id === metadata.processId); isHealthy = !!(process && process.status === 'running'); break; } catch (error) { - this.logger.error(`Process ${metadata.processId} not found or not running, retrying...${i + 1}/3`, {error}); + this.logger.error(`Failed to check process ${metadata.processId}, retrying...${i + 1}/3`, {error}); isHealthy = false; // Process not found or not running } } @@ -1212,9 +1168,12 @@ export class SandboxSdkClient extends BaseSandboxService { } // Clean up files - await sandbox.exec(`rm -rf /app/${instanceId}`); + await this.safeSandboxExec(`rm -rf ${instanceId}`); - // Invalidate cache since instance is being shutdown + // Invalidate session cache + this.invalidateSessionCache(instanceId); + + // Invalidate metadata cache since instance is being shutdown this.invalidateMetadataCache(instanceId); return { @@ -1236,7 +1195,7 @@ export class SandboxSdkClient extends BaseSandboxService { async writeFiles(instanceId: string, files: WriteFilesRequest['files'], commitMessage?: string): Promise { try { - const sandbox = this.getSandbox(); + const session = await this.getInstanceSession(instanceId); const results = []; @@ -1246,7 +1205,7 @@ export class SandboxSdkClient extends BaseSandboxService { const filteredFiles = files.filter(file => !donttouchFiles.has(file.filePath)); - const writePromises = filteredFiles.map(file => sandbox.writeFile(`${instanceId}/${file.filePath}`, file.fileContents)); + const writePromises = filteredFiles.map(file => session.writeFile(`/workspace/${instanceId}/${file.filePath}`, file.fileContents)); const writeResults = await Promise.all(writePromises); @@ -1286,7 +1245,7 @@ export class SandboxSdkClient extends BaseSandboxService { // If code files were modified, touch vite.config.ts to trigger a rebuild if (successCount > 0 && filteredFiles.some(file => file.filePath.endsWith('.ts') || file.filePath.endsWith('.tsx'))) { - await sandbox.exec(`touch ${instanceId}/vite.config.ts`); + await session.exec(`touch vite.config.ts`); } // Try to commit @@ -1314,13 +1273,13 @@ export class SandboxSdkClient extends BaseSandboxService { async getFiles(templateOrInstanceId: string, filePaths?: string[], applyFilter: boolean = true, redactedFiles?: string[]): Promise { try { - const sandbox = this.getSandbox(); + const session = await this.getInstanceSession(templateOrInstanceId); if (!filePaths) { // Read '.important_files.json' in instance directory - const importantFiles = await sandbox.exec(`cd ${templateOrInstanceId} && jq -r '.[]' .important_files.json | while read -r path; do if [ -d "$path" ]; then find "$path" -type f; elif [ -f "$path" ]; then echo "$path"; fi; done`); + const importantFiles = await session.exec(`jq -r '.[]' .important_files.json | while read -r path; do if [ -d "$path" ]; then find "$path" -type f; elif [ -f "$path" ]; then echo "$path"; fi; done`); this.logger.info(`Read important files: stdout: ${importantFiles.stdout}, stderr: ${importantFiles.stderr}`); - filePaths = importantFiles.stdout.split('\n').filter(path => path); + filePaths = importantFiles.stdout.split('\n').filter((path: string) => path); if (!filePaths) { return { success: false, @@ -1350,9 +1309,9 @@ export class SandboxSdkClient extends BaseSandboxService { const files = []; const errors = []; - const readPromises = filePaths.map(async (filePath) => { + const readPromises = filePaths.map(async (filePath: string) => { try { - const result = await sandbox.readFile(`${templateOrInstanceId}/${filePath}`); + const result = await session.readFile(`/workspace/${templateOrInstanceId}/${filePath}`); return { result, filePath @@ -1450,10 +1409,6 @@ export class SandboxSdkClient extends BaseSandboxService { for (const command of commands) { try { const result = await this.executeCommand(instanceId, command, timeout); - if (result.exitCode === 2 && result.stderr.includes('/bin/sh: 1: cd: can\'t cd to i-')) { - throw new Error(result.stderr); - } - results.push({ command, @@ -1763,10 +1718,11 @@ export class SandboxSdkClient extends BaseSandboxService { this.logger.info(`Files retrieved for ${instanceId}`); // Create file fetcher callback + const session = await this.getInstanceSession(instanceId); const fileFetcher: FileFetcher = async (filePath: string) => { // Fetch a single file from the instance try { - const result = await this.getSandbox().readFile(`${instanceId}/${filePath}`); + const result = await session.readFile(`/workspace/${instanceId}/${filePath}`); if (result.success) { this.logger.info(`Successfully fetched file: ${filePath}`); return { @@ -1793,9 +1749,9 @@ export class SandboxSdkClient extends BaseSandboxService { analysisResult.typecheck.issues, fileFetcher ); - fixResult.modifiedFiles.forEach((file: FileObject) => { - this.getSandbox().writeFile(`${instanceId}/${file.filePath}`, file.fileContents); - }); + for (const file of fixResult.modifiedFiles) { + await session.writeFile(`/workspace/${instanceId}/${file.filePath}`, file.fileContents); + } this.logger.info(`Code fix completed for ${instanceId}`); return fixResult; } catch (error) { @@ -1835,7 +1791,6 @@ export class SandboxSdkClient extends BaseSandboxService { throw new Error('CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN must be set in environment'); } - const sandbox = this.getSandbox(); this.logger.info('Processing deployment', { instanceId }); // Step 1: Run build commands (bun run build && bunx wrangler build) @@ -1870,10 +1825,10 @@ export class SandboxSdkClient extends BaseSandboxService { // Step 3: Read worker script from dist this.logger.info('Reading worker script'); - const workerPath = `${instanceId}/dist/index.js`; - const workerFile = await sandbox.readFile(workerPath); + const session = await this.getInstanceSession(instanceId); + const workerFile = await session.readFile(`/workspace/${instanceId}/dist/index.js`); if (!workerFile.success) { - throw new Error(`Worker script not found at ${workerPath}. Please build the project first.`); + throw new Error(`Worker script not found at /${instanceId}/dist/index.js. Please build the project first.`); } const workerContent = workerFile.content; @@ -1884,16 +1839,16 @@ export class SandboxSdkClient extends BaseSandboxService { let additionalModules: Map | undefined; try { const workerAssetsPath = `${instanceId}/dist/assets`; - const workerAssetsResult = await sandbox.exec(`test -d ${workerAssetsPath} && echo "exists" || echo "missing"`); + const workerAssetsResult = await this.safeSandboxExec(`test -d ${workerAssetsPath} && echo "exists" || echo "missing"`); const hasWorkerAssets = workerAssetsResult.exitCode === 0 && workerAssetsResult.stdout.trim() === "exists"; if (hasWorkerAssets) { this.logger.info('Processing additional worker modules', { workerAssetsPath }); // Find all JS files in the worker assets directory - const findResult = await sandbox.exec(`find ${workerAssetsPath} -type f -name "*.js"`); + const findResult = await this.safeSandboxExec(`find ${workerAssetsPath} -type f -name "*.js"`); if (findResult.exitCode === 0) { - const modulePaths = findResult.stdout.trim().split('\n').filter(path => path); + const modulePaths = findResult.stdout.trim().split('\n').filter((path: string) => path.trim()); if (modulePaths.length > 0) { additionalModules = new Map(); @@ -1930,7 +1885,7 @@ export class SandboxSdkClient extends BaseSandboxService { let assetsManifest: Record | undefined; let fileContents: Map | undefined; - const assetDirResult = await sandbox.exec(`test -d ${assetsPath} && echo "exists" || echo "missing"`); + const assetDirResult = await this.safeSandboxExec(`test -d ${assetsPath} && echo "exists" || echo "missing"`); const hasAssets = assetDirResult.exitCode === 0 && assetDirResult.stdout.trim() === "exists"; if (hasAssets) { @@ -2013,15 +1968,13 @@ export class SandboxSdkClient extends BaseSandboxService { assetsManifest: Record; fileContents: Map; }> { - const sandbox = this.getSandbox(); - // Get list of all files in assets directory - const findResult = await sandbox.exec(`find ${assetsPath} -type f`); + const findResult = await this.safeSandboxExec(`find ${assetsPath} -type f`); if (findResult.exitCode !== 0) { throw new Error(`Failed to list assets: ${findResult.stderr}`); } - const filePaths = findResult.stdout.trim().split('\n').filter(path => path); + const filePaths = findResult.stdout.trim().split('\n').filter((path: string) => path); this.logger.info('Asset files found', { count: filePaths.length }); const fileContents = new Map(); @@ -2055,12 +2008,11 @@ export class SandboxSdkClient extends BaseSandboxService { /** * Read file from sandbox as base64 and convert to Buffer + * Uses default session for deployment file operations with absolute paths */ private async readFileAsBase64Buffer(filePath: string): Promise { - const sandbox = this.getSandbox(); - // Use base64 with no line wrapping (-w 0) to preserve binary data - const base64Result = await sandbox.exec(`base64 -w 0 "${filePath}"`); + const base64Result = await this.safeSandboxExec(`base64 -w 0 "${filePath}"`); if (base64Result.exitCode !== 0) { throw new Error(`Failed to encode file: ${base64Result.stderr}`); } @@ -2220,8 +2172,8 @@ export class SandboxSdkClient extends BaseSandboxService { const filesToUse = finalGitContext.allFiles.length > 0 ? finalGitContext.allFiles : finalGitContext.trackedFiles; const filesToUseSet = new Set(filesToUse); const cachedFiles = allFiles.filter(file => filesToUseSet.has(file.filePath) && file.fileContents.trim() !== '[REDACTED]'); - const cachedFilePaths = new Set(cachedFiles.map(file => file.filePath)); - const filesToFetch = filesToUse.filter(file => !cachedFilePaths.has(file)); + const cachedFilePaths = new Set(cachedFiles.map((file: {filePath: string; fileContents: string}) => file.filePath)); + const filesToFetch = filesToUse.filter((file: string) => !cachedFilePaths.has(file)); const filesNotCached = await this.getFileDirect(instanceId, filesToFetch); const files = [...cachedFiles, ...filesNotCached]; @@ -2279,12 +2231,13 @@ export class SandboxSdkClient extends BaseSandboxService { fileContents: string; }[]> { const files: { filePath: string; fileContents: string; }[] = []; + const session = await this.getInstanceSession(instanceId); this.logger.info(`Reading ${filePaths.length} files`, { instanceId }); for (const filePath of filePaths) { try { - const readResult = await this.getSandbox().readFile(`${instanceId}/${filePath}`); + const readResult = await session.readFile(`/workspace/${instanceId}/${filePath}`); if (readResult.success && readResult.content) { files.push({ filePath, @@ -2342,7 +2295,7 @@ export class SandboxSdkClient extends BaseSandboxService { if (logResult.exitCode === 0 && logResult.stdout.trim()) { const commitLines = logResult.stdout.trim().split('\n'); for (const line of commitLines) { - const [hash, message, timestamp] = line.split('|'); + const [hash, message, timestamp] = (line as string).split('|'); if (hash && message) { localCommits.push({ hash: hash.trim(), @@ -2356,13 +2309,13 @@ export class SandboxSdkClient extends BaseSandboxService { // Get git-tracked files (respects .gitignore) const lsFilesResult = await this.executeCommand(instanceId, 'git ls-files'); const trackedFiles = lsFilesResult.exitCode === 0 - ? lsFilesResult.stdout.trim().split('\n').filter(f => f.trim()) + ? lsFilesResult.stdout.trim().split('\n').filter((f: string) => f.trim()) : []; // Get untracked files (respects .gitignore) const untrackedResult = await this.executeCommand(instanceId, 'git ls-files --others --exclude-standard'); const untrackedFiles = untrackedResult.exitCode === 0 - ? untrackedResult.stdout.trim().split('\n').filter(f => f.trim()) + ? untrackedResult.stdout.trim().split('\n').filter((f: string) => f.trim()) : []; // Combine all files diff --git a/worker/services/sandbox/sandboxTypes.ts b/worker/services/sandbox/sandboxTypes.ts index ae736353..a5ab96ab 100644 --- a/worker/services/sandbox/sandboxTypes.ts +++ b/worker/services/sandbox/sandboxTypes.ts @@ -30,10 +30,11 @@ export const TemplateDetailsSchema = z.object({ usage: z.string(), }), fileTree: FileTreeNodeSchema, - files: z.array(TemplateFileSchema), + allFiles: z.record(z.string(), z.string()), // Map of filePath -> fileContents language: z.string().optional(), deps: z.record(z.string(), z.string()), frameworks: z.array(z.string()).optional(), + importantFiles: z.array(z.string()), dontTouchFiles: z.array(z.string()), redactedFiles: z.array(z.string()), }) diff --git a/worker/services/sandbox/zipExtractor.ts b/worker/services/sandbox/zipExtractor.ts new file mode 100644 index 00000000..b81d5170 --- /dev/null +++ b/worker/services/sandbox/zipExtractor.ts @@ -0,0 +1,112 @@ +import { unzipSync } from 'fflate'; +import type { TemplateFile } from './sandboxTypes'; + +/** + * In-memory zip extraction service for Cloudflare Workers + * Extracts and encodes file contents as UTF-8 strings or base64 for binary data + */ +export class ZipExtractor { + // Max uncompressed size (50MB) + private static readonly MAX_UNCOMPRESSED_SIZE = 50 * 1024 * 1024; + + /** + * Extracts all files from a zip archive + * + * Text files are decoded as UTF-8. Binary files that cannot be decoded as UTF-8 + * are encoded as base64 with a "base64:" prefix. + * + * @param zipBuffer - ArrayBuffer containing the zip file + * @returns Array of extracted files with paths and encoded contents + * @throws Error if zip is invalid or exceeds size limits + */ + static extractFiles(zipBuffer: ArrayBuffer): TemplateFile[] { + try { + const uint8Array = new Uint8Array(zipBuffer); + const unzipped = unzipSync(uint8Array); + + const files: TemplateFile[] = []; + let totalUncompressedSize = 0; + + for (const [filePath, fileData] of Object.entries(unzipped)) { + // Skip directories + if (filePath.endsWith('/')) { + continue; + } + + // Check size limits + totalUncompressedSize += fileData.byteLength; + if (totalUncompressedSize > this.MAX_UNCOMPRESSED_SIZE) { + throw new Error( + `Total uncompressed size exceeds ${this.MAX_UNCOMPRESSED_SIZE / 1024 / 1024}MB limit` + ); + } + + let fileContents: string; + + // Attempt UTF-8 decoding + try { + const decoder = new TextDecoder('utf-8'); + fileContents = decoder.decode(fileData); + + // Replacement character indicates invalid UTF-8 sequence (binary data) + if (fileContents.includes('\uFFFD')) { + throw new Error('Contains replacement characters'); + } + } catch (error) { + // Binary file detected, encode as base64 + const binaryString = Array.from(fileData) + .map(byte => String.fromCharCode(byte)) + .join(''); + fileContents = `base64:${btoa(binaryString)}`; + } + + files.push({ + filePath, + fileContents + }); + } + + return files; + } catch (error) { + if (error instanceof Error) { + throw new Error(`Failed to extract zip: ${error.message}`); + } + throw new Error('Failed to extract zip: Unknown error'); + } + } + + /** + * Decodes file contents to bytes + * + * Handles both base64-encoded binary data and UTF-8 text + * + * @param fileContents - File content string (may be base64-prefixed) + * @returns Byte array representation of the file + */ + static decodeFileContents(fileContents: string): Uint8Array { + if (fileContents.startsWith('base64:')) { + const base64Data = fileContents.substring(7); + const binaryString = atob(base64Data); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; + } + + // UTF-8 text content + const encoder = new TextEncoder(); + return encoder.encode(fileContents); + } + + /** + * Checks if file contents are base64-encoded + * + * @param fileContents - File content string + * @returns True if content is base64-encoded, false otherwise + */ + static isBinaryContent(fileContents: string): boolean { + return fileContents.startsWith('base64:'); + } + +} From 262bf691ace3b66212128f18278e8f9fe348a0ec Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Mon, 20 Oct 2025 16:14:46 -0400 Subject: [PATCH 043/150] fix: handle parent dir creation in sandbox --- worker/services/sandbox/sandboxSdkClient.ts | 85 ++++++++++++--------- 1 file changed, 51 insertions(+), 34 deletions(-) diff --git a/worker/services/sandbox/sandboxSdkClient.ts b/worker/services/sandbox/sandboxSdkClient.ts index 97a77379..2f21e956 100644 --- a/worker/services/sandbox/sandboxSdkClient.ts +++ b/worker/services/sandbox/sandboxSdkClient.ts @@ -1,4 +1,4 @@ -import { getSandbox, Sandbox, parseSSEStream, LogEvent } from '@cloudflare/sandbox'; +import { getSandbox, Sandbox, parseSSEStream, LogEvent, ExecResult } from '@cloudflare/sandbox'; import { TemplateDetailsResponse, @@ -70,6 +70,8 @@ interface InstanceMetadata { type SandboxType = DurableObjectStub>; +type ExecutionSession = Awaited>; + /** * Streaming event for enhanced command execution */ @@ -109,8 +111,7 @@ function getAutoAllocatedSandbox(sessionId: string): string { export class SandboxSdkClient extends BaseSandboxService { private sandbox: SandboxType; private metadataCache = new Map(); - private sessionCache = new Map>>(); - private defaultSession: Awaited> | null = null; + private sessionCache = new Map(); constructor(sandboxId: string, agentId: string) { if (env.ALLOCATION_STRATEGY === AllocationStrategy.MANY_TO_ONE) { @@ -154,7 +155,7 @@ export class SandboxSdkClient extends BaseSandboxService { * Generic session getter with caching and automatic recovery * Properly handles existing sessions and ensures correct cwd */ - private async getOrCreateSession(sessionId: string, cwd: string): Promise>> { + private async getOrCreateSession(sessionId: string, cwd: string): Promise { try { // Try to create a new session with the specified cwd this.logger.info('Creating new session', { sessionId, cwd }); @@ -189,35 +190,36 @@ export class SandboxSdkClient extends BaseSandboxService { } /** - * Get or create default session for anonymous sandbox operations + * Get or create a session for an instance with automatic caching. + * Environment variables should be set via .dev.vars file. */ - private async getDefaultSession() { - if (!this.defaultSession) { - this.defaultSession = await this.getOrCreateSession('sandbox-default', '/workspace'); + private async getInstanceSession(instanceId: string, cwd?: string): Promise { + if (!this.sessionCache.has(instanceId)) { + cwd = cwd || `/workspace/${instanceId}`; + const session = await this.getOrCreateSession(instanceId, cwd); + this.sessionCache.set(instanceId, session); } - return this.defaultSession; + const session = this.sessionCache.get(instanceId); + return session!; } /** - * Safe wrapper for direct sandbox exec calls using default session + * Get or create default session for anonymous sandbox operations */ - private async safeSandboxExec(command: string, options?: {timeout?: number}): Promise>> { - const session = await this.getDefaultSession(); + private async getDefaultSession(): Promise { + return await this.getInstanceSession('sandbox-default', '/workspace'); + } + + private async executeCommand(instanceId: string, command: string, options?: {timeout?: number}): Promise { + const session = await this.getInstanceSession(instanceId); return await session.exec(command, options); } /** - * Get or create a session for an instance with automatic caching. - * Environment variables should be set via .dev.vars file. + * Safe wrapper for direct sandbox exec calls using default session */ - private async getInstanceSession(instanceId: string) { - if (!this.sessionCache.has(instanceId)) { - const cwd = `/workspace/${instanceId}`; - const session = await this.getOrCreateSession(instanceId, cwd); - this.sessionCache.set(instanceId, session); - } - const session = this.sessionCache.get(instanceId); - return session!; + private async safeSandboxExec(command: string, options?: {timeout?: number}): Promise { + return await this.executeCommand('sandbox-default', command, options); } /** @@ -249,6 +251,20 @@ export class SandboxSdkClient extends BaseSandboxService { } } + private async writeFile(targetPath: string, data: string, session?: ExecutionSession) { + if (!session) { + session = await this.getDefaultSession() + } + + // Ensure parent directory exists (mkdir -p is idempotent) + const dir = targetPath.substring(0, targetPath.lastIndexOf('/')); + if (dir) { + await session.exec(`mkdir -p "${dir}"`); + } + + return await session.writeFile(targetPath, data); + } + async updateProjectName(instanceId: string, projectName: string): Promise { try { await this.updateProjectConfiguration(instanceId, projectName); @@ -270,11 +286,6 @@ export class SandboxSdkClient extends BaseSandboxService { return `${instanceId}-metadata.json`; } - private async executeCommand(instanceId: string, command: string, timeout?: number): Promise>['exec']>>> { - const session = await this.getInstanceSession(instanceId); - return await session.exec(command, { timeout }); - } - private async getInstanceMetadata(instanceId: string): Promise { // Check cache first if (this.metadataCache.has(instanceId)) { @@ -425,6 +436,11 @@ export class SandboxSdkClient extends BaseSandboxService { } catch { this.logger.info('No package.json found', { templateName }); } + + const allFiles = filesResponse.files.reduce((acc, file) => { + acc[file.filePath] = file.fileContents; + return acc; + }, {} as Record); const templateDetails: TemplateDetails = { name: templateName, description: { @@ -432,7 +448,8 @@ export class SandboxSdkClient extends BaseSandboxService { usage: catalogInfo?.description.usage || '' }, fileTree, - files: filesResponse.files, + allFiles, + importantFiles: filesResponse.files.map(file => file.filePath), language: catalogInfo?.language, deps: dependencies, dontTouchFiles, @@ -930,7 +947,7 @@ export class SandboxSdkClient extends BaseSandboxService { this.logger.info('Installing dependencies', { instanceId }); const [installResult, tunnelURL] = await Promise.all([ - this.executeCommand(instanceId, `bun install`, 40000), + this.executeCommand(instanceId, `bun install`, { timeout: 40000 }), tunnelUrlPromise ]); this.logger.info('Dependencies installed', { instanceId, tunnelURL }); @@ -1278,7 +1295,7 @@ export class SandboxSdkClient extends BaseSandboxService { const filteredFiles = files.filter(file => !donttouchFiles.has(file.filePath)); - const writePromises = filteredFiles.map(file => session.writeFile(`/workspace/${instanceId}/${file.filePath}`, file.fileContents)); + const writePromises = filteredFiles.map(file => this.writeFile(`/workspace/${instanceId}/${file.filePath}`, file.fileContents, session)); const writeResults = await Promise.all(writePromises); @@ -1449,7 +1466,7 @@ export class SandboxSdkClient extends BaseSandboxService { // Use CLI to get all logs and reset the file const durationArg = durationSeconds ? `--duration ${durationSeconds}` : ''; const cmd = `timeout 10s monitor-cli logs get -i ${instanceId} --format raw ${onlyRecent ? '--reset' : ''} ${durationArg}`; - const result = await this.executeCommand(instanceId, cmd, 15000); + const result = await this.executeCommand(instanceId, cmd, { timeout: 15000 }); return { success: true, logs: { @@ -1481,7 +1498,7 @@ export class SandboxSdkClient extends BaseSandboxService { for (const command of commands) { try { - const result = await this.executeCommand(instanceId, command, timeout); + const result = await this.executeCommand(instanceId, command, { timeout }); results.push({ command, @@ -1536,7 +1553,7 @@ export class SandboxSdkClient extends BaseSandboxService { try { let errors: RuntimeError[] = []; const cmd = `timeout 3s monitor-cli errors list -i ${instanceId} --format json ${clear ? '--reset' : ''}`; - const result = await this.executeCommand(instanceId, cmd, 15000); + const result = await this.executeCommand(instanceId, cmd, { timeout: 15000 }); if (result.exitCode === 0) { let response: {success: boolean, errors: StoredError[]}; @@ -1584,7 +1601,7 @@ export class SandboxSdkClient extends BaseSandboxService { // Try enhanced error system first - clear ALL errors try { const cmd = `timeout 10s monitor-cli errors clear -i ${instanceId} --confirm`; - const result = await this.executeCommand(instanceId, cmd, 15000); // 15 second timeout + const result = await this.executeCommand(instanceId, cmd, { timeout: 15000 }); // 15 second timeout if (result.exitCode === 0) { let response: any; From fad85df3b1385426fb3892d74fb97f9f4dd2d52d Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Mon, 20 Oct 2025 16:18:08 -0400 Subject: [PATCH 044/150] feat: alter template schema to return map instead of list of files --- worker/agents/core/simpleGeneratorAgent.ts | 7 ++-- worker/agents/core/stateMigration.ts | 16 ++++---- .../services/implementations/FileManager.ts | 41 ++----------------- .../services/interfaces/IFileManager.ts | 19 +-------- worker/api/controllers/agent/controller.ts | 3 +- worker/services/sandbox/sandboxTypes.ts | 3 +- worker/services/sandbox/utils.ts | 8 ++++ 7 files changed, 29 insertions(+), 68 deletions(-) create mode 100644 worker/services/sandbox/utils.ts diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index 439f7346..700bd2c2 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -210,8 +210,7 @@ export class SimpleCodeGeneratorAgent extends Agent { } }) - const packageJsonFile = templateInfo.templateDetails?.files.find(file => file.filePath === 'package.json'); - const packageJson = packageJsonFile ? packageJsonFile.fileContents : ''; + const packageJson = templateInfo.templateDetails?.allFiles['package.json']; this.setState({ ...this.initialState, @@ -1584,7 +1583,7 @@ export class SimpleCodeGeneratorAgent extends Agent { let fileContents = ''; let filePurpose = ''; try { - const fmFile = this.fileManager.getFile(path); + const fmFile = this.fileManager.getGeneratedFile(path); if (fmFile) { fileContents = fmFile.fileContents; filePurpose = fmFile.filePurpose || ''; @@ -1981,7 +1980,7 @@ export class SimpleCodeGeneratorAgent extends Agent { }); // Prepare README with Cloudflare button BEFORE push (if it exists) - const readmeFile = this.fileManager.getFile('README.md'); + const readmeFile = this.fileManager.getGeneratedFile('README.md'); if (readmeFile && readmeFile.fileContents.includes('[cloudflarebutton]')) { readmeFile.fileContents = readmeFile.fileContents.replaceAll( '[cloudflarebutton]', diff --git a/worker/agents/core/stateMigration.ts b/worker/agents/core/stateMigration.ts index 9e60b6ba..30c4338a 100644 --- a/worker/agents/core/stateMigration.ts +++ b/worker/agents/core/stateMigration.ts @@ -35,19 +35,21 @@ export class StateMigration { } let migratedTemplateDetails = state.templateDetails; - if (migratedTemplateDetails?.files) { - const migratedTemplateFiles = migratedTemplateDetails.files.map(file => { + if ('files' in migratedTemplateDetails && migratedTemplateDetails?.files) { + const migratedTemplateFiles = (migratedTemplateDetails.files as Array).map(file => { const migratedFile = migrateFile(file); - if (migratedFile !== file) { - needsMigration = true; - } return migratedFile; }); + + const allFiles = migratedTemplateFiles.reduce((acc, file) => { + acc[file.filePath] = file; + return acc; + }, {} as Record); if (needsMigration) { migratedTemplateDetails = { ...migratedTemplateDetails, - files: migratedTemplateFiles + allFiles }; } } @@ -151,7 +153,7 @@ export class StateMigration { if (needsMigration) { logger.info('Migrating state: schema format, conversation cleanup, and security fixes', { generatedFilesCount: Object.keys(migratedFilesMap).length, - templateFilesCount: migratedTemplateDetails?.files?.length || 0, + templateFilesCount: migratedTemplateDetails?.allFiles?.length || 0, finalConversationCount: migratedConversationMessages?.length || 0, removedUserApiKeys: state.inferenceContext && 'userApiKeys' in state.inferenceContext }); diff --git a/worker/agents/services/implementations/FileManager.ts b/worker/agents/services/implementations/FileManager.ts index 3107af1a..60d72bde 100644 --- a/worker/agents/services/implementations/FileManager.ts +++ b/worker/agents/services/implementations/FileManager.ts @@ -2,7 +2,7 @@ import * as Diff from 'diff'; import { IFileManager } from '../interfaces/IFileManager'; import { IStateManager } from '../interfaces/IStateManager'; import { FileOutputType } from '../../schemas'; -import { TemplateDetails } from '../../../services/sandbox/sandboxTypes'; +// import { TemplateDetails } from '../../../services/sandbox/sandboxTypes'; import { FileProcessing } from '../../domain/pure/FileProcessing'; import { FileState } from 'worker/agents/core/state'; @@ -15,11 +15,6 @@ export class FileManager implements IFileManager { private stateManager: IStateManager ) {} - getTemplateFile(path: string): { filePath: string; fileContents: string } | null { - const state = this.stateManager.getState(); - return state.templateDetails?.files?.find(file => file.filePath === path) || null; - } - getGeneratedFile(path: string): FileOutputType | null { const state = this.stateManager.getState(); return state.generatedFilesMap[path] || null; @@ -44,7 +39,8 @@ export class FileManager implements IFileManager { const oldFile = filesMap[file.filePath]; // Get comparison base: from generatedFilesMap, template/filesystem, or empty string for new files - const oldFileContents = oldFile?.fileContents ?? (this.getFileContents(file.filePath) || ''); + // TODO: fix checking against template files + const oldFileContents = oldFile?.fileContents ?? (this.getGeneratedFile(file.filePath)?.fileContents || ''); // Generate diff if contents changed if (oldFileContents !== file.fileContents) { @@ -90,32 +86,8 @@ export class FileManager implements IFileManager { generatedFilesMap: newFilesMap }); } - - getFile(path: string): FileOutputType | null { - const generatedFile = this.getGeneratedFile(path); - if (generatedFile) { - return generatedFile; - } - - const templateFile = this.getTemplateFile(path); - if (!templateFile) { - return null; - } - return {...templateFile, filePurpose: 'Template file'}; - } - - getFileContents(path: string): string { - const generatedFile = this.getGeneratedFile(path); - if (generatedFile) { - return generatedFile.fileContents; - } - - const templateFile = this.getTemplateFile(path); - return templateFile?.fileContents || ''; - } - fileExists(path: string): boolean { - return !!this.getGeneratedFile(path) || !!this.getTemplateFile(path); + return !!this.getGeneratedFile(path) } getGeneratedFilePaths(): string[] { @@ -123,11 +95,6 @@ export class FileManager implements IFileManager { return Object.keys(state.generatedFilesMap); } - getTemplateDetails(): TemplateDetails | undefined { - const state = this.stateManager.getState(); - return state.templateDetails; - } - getGeneratedFilesMap(): Record { const state = this.stateManager.getState(); return state.generatedFilesMap; diff --git a/worker/agents/services/interfaces/IFileManager.ts b/worker/agents/services/interfaces/IFileManager.ts index 4bca5afc..58e8e5f1 100644 --- a/worker/agents/services/interfaces/IFileManager.ts +++ b/worker/agents/services/interfaces/IFileManager.ts @@ -1,16 +1,11 @@ import { FileOutputType } from '../../schemas'; -import { TemplateDetails } from '../../../services/sandbox/sandboxTypes'; +// import { TemplateDetails } from '../../../services/sandbox/sandboxTypes'; /** * Interface for file management operations * Abstracts file storage and retrieval */ export interface IFileManager { - /** - * Get a template file by path - */ - getTemplateFile(path: string): { filePath: string; fileContents: string } | null; - /** * Get a generated file by path */ @@ -35,12 +30,6 @@ export interface IFileManager { * Delete files from the file manager */ deleteFiles(filePaths: string[]): void; - - /** - * Get file contents by path (template or generated) - */ - getFileContents(path: string): string; - /** * Check if file exists (template or generated) */ @@ -50,12 +39,6 @@ export interface IFileManager { * Get all generated file paths */ getGeneratedFilePaths(): string[]; - - /** - * Get template details - */ - getTemplateDetails(): TemplateDetails | undefined; - /** * Get generated files map */ diff --git a/worker/api/controllers/agent/controller.ts b/worker/api/controllers/agent/controller.ts index f2d7a3ee..2f4326cb 100644 --- a/worker/api/controllers/agent/controller.ts +++ b/worker/api/controllers/agent/controller.ts @@ -14,6 +14,7 @@ import { createLogger } from '../../../logger'; import { getPreviewDomain } from 'worker/utils/urls'; import { ImageType, uploadImage } from 'worker/utils/images'; import { ProcessedImageAttachment } from 'worker/types/image-attachment'; +import { getTemplateImportantFiles } from 'worker/services/sandbox/utils'; const defaultCodeGenArgs: CodeGenArgs = { query: '', @@ -129,7 +130,7 @@ export class CodingAgentController extends BaseController { httpStatusUrl, template: { name: templateDetails.name, - files: templateDetails.files, + files: getTemplateImportantFiles(templateDetails), } }); diff --git a/worker/services/sandbox/sandboxTypes.ts b/worker/services/sandbox/sandboxTypes.ts index ae736353..a5ab96ab 100644 --- a/worker/services/sandbox/sandboxTypes.ts +++ b/worker/services/sandbox/sandboxTypes.ts @@ -30,10 +30,11 @@ export const TemplateDetailsSchema = z.object({ usage: z.string(), }), fileTree: FileTreeNodeSchema, - files: z.array(TemplateFileSchema), + allFiles: z.record(z.string(), z.string()), // Map of filePath -> fileContents language: z.string().optional(), deps: z.record(z.string(), z.string()), frameworks: z.array(z.string()).optional(), + importantFiles: z.array(z.string()), dontTouchFiles: z.array(z.string()), redactedFiles: z.array(z.string()), }) diff --git a/worker/services/sandbox/utils.ts b/worker/services/sandbox/utils.ts new file mode 100644 index 00000000..2796564b --- /dev/null +++ b/worker/services/sandbox/utils.ts @@ -0,0 +1,8 @@ +import { TemplateDetails, TemplateFile } from "./sandboxTypes"; + +export function getTemplateImportantFiles(templateDetails: TemplateDetails): TemplateFile[] { + return templateDetails.importantFiles.map(filePath => ({ + filePath, + fileContents: templateDetails.allFiles[filePath], + })); +} \ No newline at end of file From e28b0012cfe7f6589c7cc1022e9179eac55c0552 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Mon, 20 Oct 2025 16:22:44 -0400 Subject: [PATCH 045/150] chore: upgrade agents package from 0.1.6 to 0.2.14 --- bun.lock | 782 ++------------------------------------------------- package.json | 2 +- 2 files changed, 19 insertions(+), 765 deletions(-) diff --git a/bun.lock b/bun.lock index bf6c1b8e..ed18fc2f 100644 --- a/bun.lock +++ b/bun.lock @@ -43,7 +43,7 @@ "@typescript-eslint/typescript-estree": "^8.44.1", "@vitejs/plugin-react": "^5.0.4", "@vitejs/plugin-react-oxc": "^0.4.2", - "agents": "^0.1.6", + "agents": "^0.2.14", "chalk": "^5.6.2", "class-variance-authority": "^0.7.1", "cloudflare": "^4.5.0", @@ -130,13 +130,15 @@ "vite": "npm:rolldown-vite@latest", }, "packages": { - "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="], + "@ai-sdk/gateway": ["@ai-sdk/gateway@1.0.39", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.12", "@vercel/oidc": "3.0.2" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ijYCKG2sbn2RBVfIgaXNXvzHAf2HpFXxQODtjMI+T7Z4CLryflytchsZZ9qrGtsjiQVopKOV6m6kj4lq5fnbsg=="], - "@ai-sdk/gateway": ["@ai-sdk/gateway@1.0.25", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.9" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-eI/6LLmn1tWFzuhjxgcPEqUFXwLjyRuGFrwkCoqLaTKe/qMYBEAV3iddnGUM0AV+Hp4NEykzP4ly5tibOLDMXw=="], + "@ai-sdk/openai": ["@ai-sdk/openai@2.0.48", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.12" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-dIGOVtHaScTNIQzxkE4I8T5PpoutFWxonR/awdRz+5sCpoO7V2kVL44+X6piJbQIMdFYUK/h+HTX3+BjTbRHmw=="], "@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="], - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.9", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-Pm571x5efqaI4hf9yW4KsVlDBDme8++UepZRnq+kqVBWWjgvGhQlzU8glaFq0YJEB9kkxZHbRRyVeHoV2sRYaQ=="], + "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.12", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZtbdvYxdMoria+2SlNarEk6Hlgyf+zzcznlD55EAl+7VZvJaSg2sqPvwArY7L6TfDEDJsnCq0fdhBSkYo0Xqdg=="], + + "@apidevtools/json-schema-ref-parser": ["@apidevtools/json-schema-ref-parser@11.9.3", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" } }, "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ=="], "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], @@ -214,8 +216,6 @@ "@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - "@base-org/account": ["@base-org/account@1.1.1", "", { "dependencies": { "@noble/hashes": "1.4.0", "clsx": "1.2.1", "eventemitter3": "5.0.1", "idb-keyval": "6.2.1", "ox": "0.6.9", "preact": "10.24.2", "viem": "^2.31.7", "zustand": "5.0.3" } }, "sha512-IfVJPrDPhHfqXRDb89472hXkpvJuQQR7FDI9isLPHEqSYt/45whIoBxSPgZ0ssTt379VhQo4+87PWI1DoLSfAQ=="], - "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="], "@cloudflare/containers": ["@cloudflare/containers@0.0.28", "", {}, "sha512-wzR9UWcGvZ9znd4elkXklilPcHX6srncsjSkx696SZRZyTygNbWsLlHegvc1C+e9gn28HRZU3dLiAzXiC9IY1w=="], @@ -242,16 +242,12 @@ "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250911.0", "", {}, "sha512-Ardq4aDdNOfnvU/qC8anznZYSsMlfZnMgLAdwxESf3bMdgkb+GV01LpY8NzridFe7cFeprfiDNANBZoeUeEDlg=="], - "@coinbase/wallet-sdk": ["@coinbase/wallet-sdk@4.3.6", "", { "dependencies": { "@noble/hashes": "1.4.0", "clsx": "1.2.1", "eventemitter3": "5.0.1", "idb-keyval": "6.2.1", "ox": "0.6.9", "preact": "10.24.2", "viem": "^2.27.2", "zustand": "5.0.3" } }, "sha512-4q8BNG1ViL4mSAAvPAtpwlOs1gpC+67eQtgIwNvT3xyeyFFd+guwkc8bcX5rTmQhXpqnhzC4f0obACbP9CqMSA=="], - "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], "@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="], "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], - "@ecies/ciphers": ["@ecies/ciphers@0.2.4", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-t+iX+Wf5nRKyNzk8dviW3Ikb/280+aEJAnw9YXvCp2tYGPSkMki+NRY+8aNLmVFv3eNtMdvViPNOPxS8SZNP+w=="], - "@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="], "@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="], @@ -332,14 +328,6 @@ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.5", "", { "dependencies": { "@eslint/core": "^0.15.2", "levn": "^0.4.1" } }, "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w=="], - "@ethereumjs/common": ["@ethereumjs/common@3.2.0", "", { "dependencies": { "@ethereumjs/util": "^8.1.0", "crc-32": "^1.2.0" } }, "sha512-pksvzI0VyLgmuEF2FA/JR/4/y6hcPq8OUail3/AvycBaW1d5VSauOZzqGvJ3RTmR4MU35lWE8KseKOsEhrFRBA=="], - - "@ethereumjs/rlp": ["@ethereumjs/rlp@4.0.1", "", { "bin": { "rlp": "bin/rlp" } }, "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw=="], - - "@ethereumjs/tx": ["@ethereumjs/tx@4.2.0", "", { "dependencies": { "@ethereumjs/common": "^3.2.0", "@ethereumjs/rlp": "^4.0.1", "@ethereumjs/util": "^8.1.0", "ethereum-cryptography": "^2.0.0" } }, "sha512-1nc6VO4jtFd172BbSnTnDQVr9IYBFl1y4xPzZdtkrkKIncBCkdbgfdRV+MiTkJYAtTxvV12GRZLqBFT1PNK6Yw=="], - - "@ethereumjs/util": ["@ethereumjs/util@8.1.0", "", { "dependencies": { "@ethereumjs/rlp": "^4.0.1", "ethereum-cryptography": "^2.0.0", "micro-ftch": "^0.3.1" } }, "sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA=="], - "@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="], "@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="], @@ -348,8 +336,6 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], - "@gemini-wallet/core": ["@gemini-wallet/core@0.2.0", "", { "dependencies": { "@metamask/rpc-errors": "7.0.2", "eventemitter3": "5.0.1" }, "peerDependencies": { "viem": ">=2.0.0" } }, "sha512-vv9aozWnKrrPWQ3vIFcWk7yta4hQW1Ie0fsNNPeXnjAxkbXr2hqMagEptLuMxpEP2W3mnRu05VDNKzcvAuuZDw=="], - "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], @@ -446,48 +432,14 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@lit-labs/ssr-dom-shim": ["@lit-labs/ssr-dom-shim@1.4.0", "", {}, "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw=="], - - "@lit/reactive-element": ["@lit/reactive-element@2.1.1", "", { "dependencies": { "@lit-labs/ssr-dom-shim": "^1.4.0" } }, "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg=="], - - "@metamask/eth-json-rpc-provider": ["@metamask/eth-json-rpc-provider@1.0.1", "", { "dependencies": { "@metamask/json-rpc-engine": "^7.0.0", "@metamask/safe-event-emitter": "^3.0.0", "@metamask/utils": "^5.0.1" } }, "sha512-whiUMPlAOrVGmX8aKYVPvlKyG4CpQXiNNyt74vE1xb5sPvmx5oA7B/kOi/JdBvhGQq97U1/AVdXEdk2zkP8qyA=="], - - "@metamask/json-rpc-engine": ["@metamask/json-rpc-engine@8.0.2", "", { "dependencies": { "@metamask/rpc-errors": "^6.2.1", "@metamask/safe-event-emitter": "^3.0.0", "@metamask/utils": "^8.3.0" } }, "sha512-IoQPmql8q7ABLruW7i4EYVHWUbF74yrp63bRuXV5Zf9BQwcn5H9Ww1eLtROYvI1bUXwOiHZ6qT5CWTrDc/t/AA=="], - - "@metamask/json-rpc-middleware-stream": ["@metamask/json-rpc-middleware-stream@7.0.2", "", { "dependencies": { "@metamask/json-rpc-engine": "^8.0.2", "@metamask/safe-event-emitter": "^3.0.0", "@metamask/utils": "^8.3.0", "readable-stream": "^3.6.2" } }, "sha512-yUdzsJK04Ev98Ck4D7lmRNQ8FPioXYhEUZOMS01LXW8qTvPGiRVXmVltj2p4wrLkh0vW7u6nv0mNl5xzC5Qmfg=="], - - "@metamask/object-multiplex": ["@metamask/object-multiplex@2.1.0", "", { "dependencies": { "once": "^1.4.0", "readable-stream": "^3.6.2" } }, "sha512-4vKIiv0DQxljcXwfpnbsXcfa5glMj5Zg9mqn4xpIWqkv6uJ2ma5/GtUfLFSxhlxnR8asRMv8dDmWya1Tc1sDFA=="], - - "@metamask/onboarding": ["@metamask/onboarding@1.0.1", "", { "dependencies": { "bowser": "^2.9.0" } }, "sha512-FqHhAsCI+Vacx2qa5mAFcWNSrTcVGMNjzxVgaX8ECSny/BJ9/vgXP9V7WF/8vb9DltPeQkxr+Fnfmm6GHfmdTQ=="], - - "@metamask/providers": ["@metamask/providers@16.1.0", "", { "dependencies": { "@metamask/json-rpc-engine": "^8.0.1", "@metamask/json-rpc-middleware-stream": "^7.0.1", "@metamask/object-multiplex": "^2.0.0", "@metamask/rpc-errors": "^6.2.1", "@metamask/safe-event-emitter": "^3.1.1", "@metamask/utils": "^8.3.0", "detect-browser": "^5.2.0", "extension-port-stream": "^3.0.0", "fast-deep-equal": "^3.1.3", "is-stream": "^2.0.0", "readable-stream": "^3.6.2", "webextension-polyfill": "^0.10.0" } }, "sha512-znVCvux30+3SaUwcUGaSf+pUckzT5ukPRpcBmy+muBLC0yaWnBcvDqGfcsw6CBIenUdFrVoAFa8B6jsuCY/a+g=="], - - "@metamask/rpc-errors": ["@metamask/rpc-errors@7.0.2", "", { "dependencies": { "@metamask/utils": "^11.0.1", "fast-safe-stringify": "^2.0.6" } }, "sha512-YYYHsVYd46XwY2QZzpGeU4PSdRhHdxnzkB8piWGvJW2xbikZ3R+epAYEL4q/K8bh9JPTucsUdwRFnACor1aOYw=="], - - "@metamask/safe-event-emitter": ["@metamask/safe-event-emitter@3.1.2", "", {}, "sha512-5yb2gMI1BDm0JybZezeoX/3XhPDOtTbcFvpTXM9kxsoZjPZFh4XciqRbpD6N86HYZqWDhEaKUDuOyR0sQHEjMA=="], - - "@metamask/sdk": ["@metamask/sdk@0.33.1", "", { "dependencies": { "@babel/runtime": "^7.26.0", "@metamask/onboarding": "^1.0.1", "@metamask/providers": "16.1.0", "@metamask/sdk-analytics": "0.0.5", "@metamask/sdk-communication-layer": "0.33.1", "@metamask/sdk-install-modal-web": "0.32.1", "@paulmillr/qr": "^0.2.1", "bowser": "^2.9.0", "cross-fetch": "^4.0.0", "debug": "4.3.4", "eciesjs": "^0.4.11", "eth-rpc-errors": "^4.0.3", "eventemitter2": "^6.4.9", "obj-multiplex": "^1.0.0", "pump": "^3.0.0", "readable-stream": "^3.6.2", "socket.io-client": "^4.5.1", "tslib": "^2.6.0", "util": "^0.12.4", "uuid": "^8.3.2" } }, "sha512-1mcOQVGr9rSrVcbKPNVzbZ8eCl1K0FATsYH3WJ/MH4WcZDWGECWrXJPNMZoEAkLxWiMe8jOQBumg2pmcDa9zpQ=="], - - "@metamask/sdk-analytics": ["@metamask/sdk-analytics@0.0.5", "", { "dependencies": { "openapi-fetch": "^0.13.5" } }, "sha512-fDah+keS1RjSUlC8GmYXvx6Y26s3Ax1U9hGpWb6GSY5SAdmTSIqp2CvYy6yW0WgLhnYhW+6xERuD0eVqV63QIQ=="], - - "@metamask/sdk-communication-layer": ["@metamask/sdk-communication-layer@0.33.1", "", { "dependencies": { "@metamask/sdk-analytics": "0.0.5", "bufferutil": "^4.0.8", "date-fns": "^2.29.3", "debug": "4.3.4", "utf-8-validate": "^5.0.2", "uuid": "^8.3.2" }, "peerDependencies": { "cross-fetch": "^4.0.0", "eciesjs": "*", "eventemitter2": "^6.4.9", "readable-stream": "^3.6.2", "socket.io-client": "^4.5.1" } }, "sha512-0bI9hkysxcfbZ/lk0T2+aKVo1j0ynQVTuB3sJ5ssPWlz+Z3VwveCkP1O7EVu1tsVVCb0YV5WxK9zmURu2FIiaA=="], - - "@metamask/sdk-install-modal-web": ["@metamask/sdk-install-modal-web@0.32.1", "", { "dependencies": { "@paulmillr/qr": "^0.2.1" } }, "sha512-MGmAo6qSjf1tuYXhCu2EZLftq+DSt5Z7fsIKr2P+lDgdTPWgLfZB1tJKzNcwKKOdf6q9Qmmxn7lJuI/gq5LrKw=="], - - "@metamask/superstruct": ["@metamask/superstruct@3.2.1", "", {}, "sha512-fLgJnDOXFmuVlB38rUN5SmU7hAFQcCjrg3Vrxz67KTY7YHFnSNEKvX4avmEBdOI0yTCxZjwMCFEqsC8k2+Wd3g=="], - - "@metamask/utils": ["@metamask/utils@11.8.1", "", { "dependencies": { "@ethereumjs/tx": "^4.2.0", "@metamask/superstruct": "^3.1.0", "@noble/hashes": "^1.3.1", "@scure/base": "^1.1.3", "@types/debug": "^4.1.7", "@types/lodash": "^4.17.20", "debug": "^4.3.4", "lodash": "^4.17.21", "pony-cause": "^2.1.10", "semver": "^7.5.4", "uuid": "^9.0.1" } }, "sha512-DIbsNUyqWLFgqJlZxi1OOCMYvI23GqFCvNJAtzv8/WXWzJfnJnvp1M24j7VvUe3URBi3S86UgQ7+7aWU9p/cnQ=="], + "@jsdevtools/ono": ["@jsdevtools/ono@7.1.3", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="], - "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.18.2", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-beedclIvFcCnPrYgHsylqiYJVJ/CI47Vyc4tY8no1/Li/O8U4BTlJfy6ZwxkYwx+Mx10nrgwSVrA7VBbhh4slg=="], + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.20.1", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-j/P+yuxXfgxb+mW7OEoRCM3G47zCTDqUPivJo/VzpjbG8I9csTXtOprCf5FfOfHK4whOJny0aHuBEON+kS7CCA=="], "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.5", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg=="], "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], - "@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], - - "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], @@ -562,8 +514,6 @@ "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.8.3", "", { "os": "win32", "cpu": "x64" }, "sha512-MoathdI2zWifsGPsgnZjYplEm2NJ4pZxu8eSCYCe+8TToDRpQ+D8BTcr8Fox2AyNEQdT57l/0LzID4812+3f/A=="], - "@paulmillr/qr": ["@paulmillr/qr@0.2.1", "", {}, "sha512-IHnV6A+zxU7XwmKFinmYjUcwlyK9+xkG3/s9KcQhI9BjQKycrJ1JRO+FbNYPwZiPKW3je/DR0k7w8/gLa5eaxQ=="], - "@poppinss/colors": ["@poppinss/colors@4.1.5", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw=="], "@poppinss/dumper": ["@poppinss/dumper@0.6.4", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-iG0TIdqv8xJ3Lt9O8DrPRxw1MRLjNpoqiSGU03P/wNLP/s0ra0udPJ1J2Tx5M0J3H/cVyEgpbn8xUKRY9j59kQ=="], @@ -682,24 +632,6 @@ "@remix-run/node-fetch-server": ["@remix-run/node-fetch-server@0.8.0", "", {}, "sha512-8/sKegb4HrM6IdcQeU0KPhj9VOHm5SUqswJDHuMCS3mwbr/NRx078QDbySmn0xslahvvZoOENd7EnK40kWKxkg=="], - "@reown/appkit": ["@reown/appkit@1.7.8", "", { "dependencies": { "@reown/appkit-common": "1.7.8", "@reown/appkit-controllers": "1.7.8", "@reown/appkit-pay": "1.7.8", "@reown/appkit-polyfills": "1.7.8", "@reown/appkit-scaffold-ui": "1.7.8", "@reown/appkit-ui": "1.7.8", "@reown/appkit-utils": "1.7.8", "@reown/appkit-wallet": "1.7.8", "@walletconnect/types": "2.21.0", "@walletconnect/universal-provider": "2.21.0", "bs58": "6.0.0", "valtio": "1.13.2", "viem": ">=2.29.0" } }, "sha512-51kTleozhA618T1UvMghkhKfaPcc9JlKwLJ5uV+riHyvSoWPKPRIa5A6M1Wano5puNyW0s3fwywhyqTHSilkaA=="], - - "@reown/appkit-common": ["@reown/appkit-common@1.7.8", "", { "dependencies": { "big.js": "6.2.2", "dayjs": "1.11.13", "viem": ">=2.29.0" } }, "sha512-ridIhc/x6JOp7KbDdwGKY4zwf8/iK8EYBl+HtWrruutSLwZyVi5P8WaZa+8iajL6LcDcDF7LoyLwMTym7SRuwQ=="], - - "@reown/appkit-controllers": ["@reown/appkit-controllers@1.7.8", "", { "dependencies": { "@reown/appkit-common": "1.7.8", "@reown/appkit-wallet": "1.7.8", "@walletconnect/universal-provider": "2.21.0", "valtio": "1.13.2", "viem": ">=2.29.0" } }, "sha512-IdXlJlivrlj6m63VsGLsjtPHHsTWvKGVzWIP1fXZHVqmK+rZCBDjCi9j267Rb9/nYRGHWBtlFQhO8dK35WfeDA=="], - - "@reown/appkit-pay": ["@reown/appkit-pay@1.7.8", "", { "dependencies": { "@reown/appkit-common": "1.7.8", "@reown/appkit-controllers": "1.7.8", "@reown/appkit-ui": "1.7.8", "@reown/appkit-utils": "1.7.8", "lit": "3.3.0", "valtio": "1.13.2" } }, "sha512-OSGQ+QJkXx0FEEjlpQqIhT8zGJKOoHzVnyy/0QFrl3WrQTjCzg0L6+i91Ad5Iy1zb6V5JjqtfIFpRVRWN4M3pw=="], - - "@reown/appkit-polyfills": ["@reown/appkit-polyfills@1.7.8", "", { "dependencies": { "buffer": "6.0.3" } }, "sha512-W/kq786dcHHAuJ3IV2prRLEgD/2iOey4ueMHf1sIFjhhCGMynMkhsOhQMUH0tzodPqUgAC494z4bpIDYjwWXaA=="], - - "@reown/appkit-scaffold-ui": ["@reown/appkit-scaffold-ui@1.7.8", "", { "dependencies": { "@reown/appkit-common": "1.7.8", "@reown/appkit-controllers": "1.7.8", "@reown/appkit-ui": "1.7.8", "@reown/appkit-utils": "1.7.8", "@reown/appkit-wallet": "1.7.8", "lit": "3.3.0" } }, "sha512-RCeHhAwOrIgcvHwYlNWMcIDibdI91waaoEYBGw71inE0kDB8uZbE7tE6DAXJmDkvl0qPh+DqlC4QbJLF1FVYdQ=="], - - "@reown/appkit-ui": ["@reown/appkit-ui@1.7.8", "", { "dependencies": { "@reown/appkit-common": "1.7.8", "@reown/appkit-controllers": "1.7.8", "@reown/appkit-wallet": "1.7.8", "lit": "3.3.0", "qrcode": "1.5.3" } }, "sha512-1hjCKjf6FLMFzrulhl0Y9Vb9Fu4royE+SXCPSWh4VhZhWqlzUFc7kutnZKx8XZFVQH4pbBvY62SpRC93gqoHow=="], - - "@reown/appkit-utils": ["@reown/appkit-utils@1.7.8", "", { "dependencies": { "@reown/appkit-common": "1.7.8", "@reown/appkit-controllers": "1.7.8", "@reown/appkit-polyfills": "1.7.8", "@reown/appkit-wallet": "1.7.8", "@walletconnect/logger": "2.1.2", "@walletconnect/universal-provider": "2.21.0", "valtio": "1.13.2", "viem": ">=2.29.0" } }, "sha512-8X7UvmE8GiaoitCwNoB86pttHgQtzy4ryHZM9kQpvjQ0ULpiER44t1qpVLXNM4X35O0v18W0Dk60DnYRMH2WRw=="], - - "@reown/appkit-wallet": ["@reown/appkit-wallet@1.7.8", "", { "dependencies": { "@reown/appkit-common": "1.7.8", "@reown/appkit-polyfills": "1.7.8", "@walletconnect/logger": "2.1.2", "zod": "3.22.4" } }, "sha512-kspz32EwHIOT/eg/ZQbFPxgXq0B/olDOj3YMu7gvLEFz4xyOFd/wgzxxAXkp5LbG4Cp++s/elh79rVNmVFdB9A=="], - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.40", "", { "os": "android", "cpu": "arm64" }, "sha512-9Ii9phC7QU6Lb+ncMfG1Xlosq0NBB1N/4sw+EGZ3y0BBWGy02TOb5ghWZalphAKv9rn1goqo5WkBjyd2YvsLmA=="], "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.40", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5O6d0y2tBQTL+ecQY3qXIwSnF1/Zik8q7LZMKeyF+VJ9l194d0IdMhl2zUF0cqWbYHuF4Pnxplk4OhurPQ/Z9Q=="], @@ -736,18 +668,6 @@ "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], - "@safe-global/safe-apps-provider": ["@safe-global/safe-apps-provider@0.18.6", "", { "dependencies": { "@safe-global/safe-apps-sdk": "^9.1.0", "events": "^3.3.0" } }, "sha512-4LhMmjPWlIO8TTDC2AwLk44XKXaK6hfBTWyljDm0HQ6TWlOEijVWNrt2s3OCVMSxlXAcEzYfqyu1daHZooTC2Q=="], - - "@safe-global/safe-apps-sdk": ["@safe-global/safe-apps-sdk@9.1.0", "", { "dependencies": { "@safe-global/safe-gateway-typescript-sdk": "^3.5.3", "viem": "^2.1.1" } }, "sha512-N5p/ulfnnA2Pi2M3YeWjULeWbjo7ei22JwU/IXnhoHzKq3pYCN6ynL9mJBOlvDVv892EgLPCWCOwQk/uBT2v0Q=="], - - "@safe-global/safe-gateway-typescript-sdk": ["@safe-global/safe-gateway-typescript-sdk@3.23.1", "", {}, "sha512-6ORQfwtEJYpalCeVO21L4XXGSdbEMfyp2hEv6cP82afKXSwvse6d3sdelgaPWUxHIsFRkWvHDdzh8IyyKHZKxw=="], - - "@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="], - - "@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], - - "@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], - "@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.15.0", "", { "dependencies": { "@sentry/core": "10.15.0" } }, "sha512-hJxo6rj3cMqiYlZd6PC8o/i2FG6hRnZdHcJkfm1HXgWCRgdCPilKghL6WU+B2H5dLyRKJ17nWjDAVQPRdCxO9w=="], "@sentry-internal/feedback": ["@sentry-internal/feedback@10.15.0", "", { "dependencies": { "@sentry/core": "10.15.0" } }, "sha512-EP+NvdU9yfmepGzQwz0jnqhd0DBxHzrP16TsJIVXJe93QJ+gumdN3XQ0lvYtEC9zHuU08DghRLjfI1kLRfGzdQ=="], @@ -796,86 +716,6 @@ "@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="], - "@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="], - - "@solana-program/compute-budget": ["@solana-program/compute-budget@0.8.0", "", { "peerDependencies": { "@solana/kit": "^2.1.0" } }, "sha512-qPKxdxaEsFxebZ4K5RPuy7VQIm/tfJLa1+Nlt3KNA8EYQkz9Xm8htdoEaXVrer9kpgzzp9R3I3Bh6omwCM06tQ=="], - - "@solana-program/token": ["@solana-program/token@0.5.1", "", { "peerDependencies": { "@solana/kit": "^2.1.0" } }, "sha512-bJvynW5q9SFuVOZ5vqGVkmaPGA0MCC+m9jgJj1nk5m20I389/ms69ASnhWGoOPNcie7S9OwBX0gTj2fiyWpfag=="], - - "@solana-program/token-2022": ["@solana-program/token-2022@0.4.2", "", { "peerDependencies": { "@solana/kit": "^2.1.0", "@solana/sysvars": "^2.1.0" } }, "sha512-zIpR5t4s9qEU3hZKupzIBxJ6nUV5/UVyIT400tu9vT1HMs5JHxaTTsb5GUhYjiiTvNwU0MQavbwc4Dl29L0Xvw=="], - - "@solana/accounts": ["@solana/accounts@2.3.0", "", { "dependencies": { "@solana/addresses": "2.3.0", "@solana/codecs-core": "2.3.0", "@solana/codecs-strings": "2.3.0", "@solana/errors": "2.3.0", "@solana/rpc-spec": "2.3.0", "@solana/rpc-types": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-QgQTj404Z6PXNOyzaOpSzjgMOuGwG8vC66jSDB+3zHaRcEPRVRd2sVSrd1U6sHtnV3aiaS6YyDuPQMheg4K2jw=="], - - "@solana/addresses": ["@solana/addresses@2.3.0", "", { "dependencies": { "@solana/assertions": "2.3.0", "@solana/codecs-core": "2.3.0", "@solana/codecs-strings": "2.3.0", "@solana/errors": "2.3.0", "@solana/nominal-types": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-ypTNkY2ZaRFpHLnHAgaW8a83N0/WoqdFvCqf4CQmnMdFsZSdC7qOwcbd7YzdaQn9dy+P2hybewzB+KP7LutxGA=="], - - "@solana/assertions": ["@solana/assertions@2.3.0", "", { "dependencies": { "@solana/errors": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-Ekoet3khNg3XFLN7MIz8W31wPQISpKUGDGTylLptI+JjCDWx3PIa88xjEMqFo02WJ8sBj2NLV64Xg1sBcsHjZQ=="], - - "@solana/codecs": ["@solana/codecs@2.3.0", "", { "dependencies": { "@solana/codecs-core": "2.3.0", "@solana/codecs-data-structures": "2.3.0", "@solana/codecs-numbers": "2.3.0", "@solana/codecs-strings": "2.3.0", "@solana/options": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-JVqGPkzoeyU262hJGdH64kNLH0M+Oew2CIPOa/9tR3++q2pEd4jU2Rxdfye9sd0Ce3XJrR5AIa8ZfbyQXzjh+g=="], - - "@solana/codecs-core": ["@solana/codecs-core@2.3.0", "", { "dependencies": { "@solana/errors": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw=="], - - "@solana/codecs-data-structures": ["@solana/codecs-data-structures@2.3.0", "", { "dependencies": { "@solana/codecs-core": "2.3.0", "@solana/codecs-numbers": "2.3.0", "@solana/errors": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-qvU5LE5DqEdYMYgELRHv+HMOx73sSoV1ZZkwIrclwUmwTbTaH8QAJURBj0RhQ/zCne7VuLLOZFFGv6jGigWhSw=="], - - "@solana/codecs-numbers": ["@solana/codecs-numbers@2.3.0", "", { "dependencies": { "@solana/codecs-core": "2.3.0", "@solana/errors": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg=="], - - "@solana/codecs-strings": ["@solana/codecs-strings@2.3.0", "", { "dependencies": { "@solana/codecs-core": "2.3.0", "@solana/codecs-numbers": "2.3.0", "@solana/errors": "2.3.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5.3.3" } }, "sha512-y5pSBYwzVziXu521hh+VxqUtp0hYGTl1eWGoc1W+8mdvBdC1kTqm/X7aYQw33J42hw03JjryvYOvmGgk3Qz/Ug=="], - - "@solana/errors": ["@solana/errors@2.3.0", "", { "dependencies": { "chalk": "^5.4.1", "commander": "^14.0.0" }, "peerDependencies": { "typescript": ">=5.3.3" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ=="], - - "@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@2.3.0", "", { "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-KfJPrMEieUg6D3hfQACoPy0ukrAV8Kio883llt/8chPEG3FVTX9z/Zuf4O01a15xZmBbmQ7toil2Dp0sxMJSxw=="], - - "@solana/functional": ["@solana/functional@2.3.0", "", { "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-AgsPh3W3tE+nK3eEw/W9qiSfTGwLYEvl0rWaxHht/lRcuDVwfKRzeSa5G79eioWFFqr+pTtoCr3D3OLkwKz02Q=="], - - "@solana/instructions": ["@solana/instructions@2.3.0", "", { "dependencies": { "@solana/codecs-core": "2.3.0", "@solana/errors": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-PLMsmaIKu7hEAzyElrk2T7JJx4D+9eRwebhFZpy2PXziNSmFF929eRHKUsKqBFM3cYR1Yy3m6roBZfA+bGE/oQ=="], - - "@solana/keys": ["@solana/keys@2.3.0", "", { "dependencies": { "@solana/assertions": "2.3.0", "@solana/codecs-core": "2.3.0", "@solana/codecs-strings": "2.3.0", "@solana/errors": "2.3.0", "@solana/nominal-types": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-ZVVdga79pNH+2pVcm6fr2sWz9HTwfopDVhYb0Lh3dh+WBmJjwkabXEIHey2rUES7NjFa/G7sV8lrUn/v8LDCCQ=="], - - "@solana/kit": ["@solana/kit@2.3.0", "", { "dependencies": { "@solana/accounts": "2.3.0", "@solana/addresses": "2.3.0", "@solana/codecs": "2.3.0", "@solana/errors": "2.3.0", "@solana/functional": "2.3.0", "@solana/instructions": "2.3.0", "@solana/keys": "2.3.0", "@solana/programs": "2.3.0", "@solana/rpc": "2.3.0", "@solana/rpc-parsed-types": "2.3.0", "@solana/rpc-spec-types": "2.3.0", "@solana/rpc-subscriptions": "2.3.0", "@solana/rpc-types": "2.3.0", "@solana/signers": "2.3.0", "@solana/sysvars": "2.3.0", "@solana/transaction-confirmation": "2.3.0", "@solana/transaction-messages": "2.3.0", "@solana/transactions": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-sb6PgwoW2LjE5oTFu4lhlS/cGt/NB3YrShEyx7JgWFWysfgLdJnhwWThgwy/4HjNsmtMrQGWVls0yVBHcMvlMQ=="], - - "@solana/nominal-types": ["@solana/nominal-types@2.3.0", "", { "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-uKlMnlP4PWW5UTXlhKM8lcgIaNj8dvd8xO4Y9l+FVvh9RvW2TO0GwUO6JCo7JBzCB0PSqRJdWWaQ8pu1Ti/OkA=="], - - "@solana/options": ["@solana/options@2.3.0", "", { "dependencies": { "@solana/codecs-core": "2.3.0", "@solana/codecs-data-structures": "2.3.0", "@solana/codecs-numbers": "2.3.0", "@solana/codecs-strings": "2.3.0", "@solana/errors": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-PPnnZBRCWWoZQ11exPxf//DRzN2C6AoFsDI/u2AsQfYih434/7Kp4XLpfOMT/XESi+gdBMFNNfbES5zg3wAIkw=="], - - "@solana/programs": ["@solana/programs@2.3.0", "", { "dependencies": { "@solana/addresses": "2.3.0", "@solana/errors": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-UXKujV71VCI5uPs+cFdwxybtHZAIZyQkqDiDnmK+DawtOO9mBn4Nimdb/6RjR2CXT78mzO9ZCZ3qfyX+ydcB7w=="], - - "@solana/promises": ["@solana/promises@2.3.0", "", { "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-GjVgutZKXVuojd9rWy1PuLnfcRfqsaCm7InCiZc8bqmJpoghlyluweNc7ml9Y5yQn1P2IOyzh9+p/77vIyNybQ=="], - - "@solana/rpc": ["@solana/rpc@2.3.0", "", { "dependencies": { "@solana/errors": "2.3.0", "@solana/fast-stable-stringify": "2.3.0", "@solana/functional": "2.3.0", "@solana/rpc-api": "2.3.0", "@solana/rpc-spec": "2.3.0", "@solana/rpc-spec-types": "2.3.0", "@solana/rpc-transformers": "2.3.0", "@solana/rpc-transport-http": "2.3.0", "@solana/rpc-types": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-ZWN76iNQAOCpYC7yKfb3UNLIMZf603JckLKOOLTHuy9MZnTN8XV6uwvDFhf42XvhglgUjGCEnbUqWtxQ9pa/pQ=="], - - "@solana/rpc-api": ["@solana/rpc-api@2.3.0", "", { "dependencies": { "@solana/addresses": "2.3.0", "@solana/codecs-core": "2.3.0", "@solana/codecs-strings": "2.3.0", "@solana/errors": "2.3.0", "@solana/keys": "2.3.0", "@solana/rpc-parsed-types": "2.3.0", "@solana/rpc-spec": "2.3.0", "@solana/rpc-transformers": "2.3.0", "@solana/rpc-types": "2.3.0", "@solana/transaction-messages": "2.3.0", "@solana/transactions": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-UUdiRfWoyYhJL9PPvFeJr4aJ554ob2jXcpn4vKmRVn9ire0sCbpQKYx6K8eEKHZWXKrDW8IDspgTl0gT/aJWVg=="], - - "@solana/rpc-parsed-types": ["@solana/rpc-parsed-types@2.3.0", "", { "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-B5pHzyEIbBJf9KHej+zdr5ZNAdSvu7WLU2lOUPh81KHdHQs6dEb310LGxcpCc7HVE8IEdO20AbckewDiAN6OCg=="], - - "@solana/rpc-spec": ["@solana/rpc-spec@2.3.0", "", { "dependencies": { "@solana/errors": "2.3.0", "@solana/rpc-spec-types": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-fA2LMX4BMixCrNB2n6T83AvjZ3oUQTu7qyPLyt8gHQaoEAXs8k6GZmu6iYcr+FboQCjUmRPgMaABbcr9j2J9Sw=="], - - "@solana/rpc-spec-types": ["@solana/rpc-spec-types@2.3.0", "", { "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-xQsb65lahjr8Wc9dMtP7xa0ZmDS8dOE2ncYjlvfyw/h4mpdXTUdrSMi6RtFwX33/rGuztQ7Hwaid5xLNSLvsFQ=="], - - "@solana/rpc-subscriptions": ["@solana/rpc-subscriptions@2.3.0", "", { "dependencies": { "@solana/errors": "2.3.0", "@solana/fast-stable-stringify": "2.3.0", "@solana/functional": "2.3.0", "@solana/promises": "2.3.0", "@solana/rpc-spec-types": "2.3.0", "@solana/rpc-subscriptions-api": "2.3.0", "@solana/rpc-subscriptions-channel-websocket": "2.3.0", "@solana/rpc-subscriptions-spec": "2.3.0", "@solana/rpc-transformers": "2.3.0", "@solana/rpc-types": "2.3.0", "@solana/subscribable": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-Uyr10nZKGVzvCOqwCZgwYrzuoDyUdwtgQRefh13pXIrdo4wYjVmoLykH49Omt6abwStB0a4UL5gX9V4mFdDJZg=="], - - "@solana/rpc-subscriptions-api": ["@solana/rpc-subscriptions-api@2.3.0", "", { "dependencies": { "@solana/addresses": "2.3.0", "@solana/keys": "2.3.0", "@solana/rpc-subscriptions-spec": "2.3.0", "@solana/rpc-transformers": "2.3.0", "@solana/rpc-types": "2.3.0", "@solana/transaction-messages": "2.3.0", "@solana/transactions": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-9mCjVbum2Hg9KGX3LKsrI5Xs0KX390lS+Z8qB80bxhar6MJPugqIPH8uRgLhCW9GN3JprAfjRNl7our8CPvsPQ=="], - - "@solana/rpc-subscriptions-channel-websocket": ["@solana/rpc-subscriptions-channel-websocket@2.3.0", "", { "dependencies": { "@solana/errors": "2.3.0", "@solana/functional": "2.3.0", "@solana/rpc-subscriptions-spec": "2.3.0", "@solana/subscribable": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3", "ws": "^8.18.0" } }, "sha512-2oL6ceFwejIgeWzbNiUHI2tZZnaOxNTSerszcin7wYQwijxtpVgUHiuItM/Y70DQmH9sKhmikQp+dqeGalaJxw=="], - - "@solana/rpc-subscriptions-spec": ["@solana/rpc-subscriptions-spec@2.3.0", "", { "dependencies": { "@solana/errors": "2.3.0", "@solana/promises": "2.3.0", "@solana/rpc-spec-types": "2.3.0", "@solana/subscribable": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-rdmVcl4PvNKQeA2l8DorIeALCgJEMSu7U8AXJS1PICeb2lQuMeaR+6cs/iowjvIB0lMVjYN2sFf6Q3dJPu6wWg=="], - - "@solana/rpc-transformers": ["@solana/rpc-transformers@2.3.0", "", { "dependencies": { "@solana/errors": "2.3.0", "@solana/functional": "2.3.0", "@solana/nominal-types": "2.3.0", "@solana/rpc-spec-types": "2.3.0", "@solana/rpc-types": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-UuHYK3XEpo9nMXdjyGKkPCOr7WsZsxs7zLYDO1A5ELH3P3JoehvrDegYRAGzBS2VKsfApZ86ZpJToP0K3PhmMA=="], - - "@solana/rpc-transport-http": ["@solana/rpc-transport-http@2.3.0", "", { "dependencies": { "@solana/errors": "2.3.0", "@solana/rpc-spec": "2.3.0", "@solana/rpc-spec-types": "2.3.0", "undici-types": "^7.11.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-HFKydmxGw8nAF5N+S0NLnPBDCe5oMDtI2RAmW8DMqP4U3Zxt2XWhvV1SNkAldT5tF0U1vP+is6fHxyhk4xqEvg=="], - - "@solana/rpc-types": ["@solana/rpc-types@2.3.0", "", { "dependencies": { "@solana/addresses": "2.3.0", "@solana/codecs-core": "2.3.0", "@solana/codecs-numbers": "2.3.0", "@solana/codecs-strings": "2.3.0", "@solana/errors": "2.3.0", "@solana/nominal-types": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-O09YX2hED2QUyGxrMOxQ9GzH1LlEwwZWu69QbL4oYmIf6P5dzEEHcqRY6L1LsDVqc/dzAdEs/E1FaPrcIaIIPw=="], - - "@solana/signers": ["@solana/signers@2.3.0", "", { "dependencies": { "@solana/addresses": "2.3.0", "@solana/codecs-core": "2.3.0", "@solana/errors": "2.3.0", "@solana/instructions": "2.3.0", "@solana/keys": "2.3.0", "@solana/nominal-types": "2.3.0", "@solana/transaction-messages": "2.3.0", "@solana/transactions": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-OSv6fGr/MFRx6J+ZChQMRqKNPGGmdjkqarKkRzkwmv7v8quWsIRnJT5EV8tBy3LI4DLO/A8vKiNSPzvm1TdaiQ=="], - - "@solana/subscribable": ["@solana/subscribable@2.3.0", "", { "dependencies": { "@solana/errors": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-DkgohEDbMkdTWiKAoatY02Njr56WXx9e/dKKfmne8/Ad6/2llUIrax78nCdlvZW9quXMaXPTxZvdQqo9N669Og=="], - - "@solana/sysvars": ["@solana/sysvars@2.3.0", "", { "dependencies": { "@solana/accounts": "2.3.0", "@solana/codecs": "2.3.0", "@solana/errors": "2.3.0", "@solana/rpc-types": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-LvjADZrpZ+CnhlHqfI5cmsRzX9Rpyb1Ox2dMHnbsRNzeKAMhu9w4ZBIaeTdO322zsTr509G1B+k2ABD3whvUBA=="], - - "@solana/transaction-confirmation": ["@solana/transaction-confirmation@2.3.0", "", { "dependencies": { "@solana/addresses": "2.3.0", "@solana/codecs-strings": "2.3.0", "@solana/errors": "2.3.0", "@solana/keys": "2.3.0", "@solana/promises": "2.3.0", "@solana/rpc": "2.3.0", "@solana/rpc-subscriptions": "2.3.0", "@solana/rpc-types": "2.3.0", "@solana/transaction-messages": "2.3.0", "@solana/transactions": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-UiEuiHCfAAZEKdfne/XljFNJbsKAe701UQHKXEInYzIgBjRbvaeYZlBmkkqtxwcasgBTOmEaEKT44J14N9VZDw=="], - - "@solana/transaction-messages": ["@solana/transaction-messages@2.3.0", "", { "dependencies": { "@solana/addresses": "2.3.0", "@solana/codecs-core": "2.3.0", "@solana/codecs-data-structures": "2.3.0", "@solana/codecs-numbers": "2.3.0", "@solana/errors": "2.3.0", "@solana/functional": "2.3.0", "@solana/instructions": "2.3.0", "@solana/nominal-types": "2.3.0", "@solana/rpc-types": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-bgqvWuy3MqKS5JdNLH649q+ngiyOu5rGS3DizSnWwYUd76RxZl1kN6CoqHSrrMzFMvis6sck/yPGG3wqrMlAww=="], - - "@solana/transactions": ["@solana/transactions@2.3.0", "", { "dependencies": { "@solana/addresses": "2.3.0", "@solana/codecs-core": "2.3.0", "@solana/codecs-data-structures": "2.3.0", "@solana/codecs-numbers": "2.3.0", "@solana/codecs-strings": "2.3.0", "@solana/errors": "2.3.0", "@solana/functional": "2.3.0", "@solana/instructions": "2.3.0", "@solana/keys": "2.3.0", "@solana/nominal-types": "2.3.0", "@solana/rpc-types": "2.3.0", "@solana/transaction-messages": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-LnTvdi8QnrQtuEZor5Msje61sDpPstTVwKg4y81tNxDhiyomjuvnSNLAq6QsB9gIxUqbNzPZgOG9IU4I4/Uaug=="], - "@speed-highlight/core": ["@speed-highlight/core@1.2.7", "", {}, "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g=="], "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], @@ -964,10 +804,6 @@ "@tailwindcss/vite": ["@tailwindcss/vite@4.1.13", "", { "dependencies": { "@tailwindcss/node": "4.1.13", "@tailwindcss/oxide": "4.1.13", "tailwindcss": "4.1.13" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-0PmqLQ010N58SbMTJ7BVJ4I2xopiQn/5i6nlb4JmxzQf8zcS5+m2Cv6tqh+sfDwtIdjoEnOvwsGQ1hkUi8QEHQ=="], - "@tanstack/query-core": ["@tanstack/query-core@5.90.2", "", {}, "sha512-k/TcR3YalnzibscALLwxeiLUub6jN5EDLwKDiO7q5f4ICEoptJ+n9+7vcEFy5/x/i6Q+Lb/tXrsKCggf5uQJXQ=="], - - "@tanstack/react-query": ["@tanstack/react-query@5.90.2", "", { "dependencies": { "@tanstack/query-core": "5.90.2" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-CLABiR+h5PYfOWr/z+vWFt5VsOA2ekQeRQBFSKlcoW6Ndx/f8rfyVmq4LbgOM4GG2qtxAxjLYLOpCNTYm4uKzw=="], - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], @@ -1038,8 +874,6 @@ "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="], - "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], - "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], "@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="], @@ -1108,6 +942,8 @@ "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="], + "@vercel/oidc": ["@vercel/oidc@3.0.2", "", {}, "sha512-JekxQ0RApo4gS4un/iMGsIL1/k4KUBe3HmnGcDvzHuFBdQdudEJgTqcsJC7y6Ul4Yw5CeykgvQbX2XeEJd0+DA=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.0.4", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.38", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA=="], "@vitejs/plugin-react-oxc": ["@vitejs/plugin-react-oxc@0.4.2", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.35" }, "peerDependencies": { "vite": "^6.3.0 || ^7.0.0" } }, "sha512-wPfjQrCQMk9J2caEN2A2yDMBS4pxBG23S1J9kzvrTS5LHkXIAA64o4kfF+f6mA7W148U+7eBrYR9awoo0t3yYA=="], @@ -1128,56 +964,6 @@ "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], - "@wagmi/connectors": ["@wagmi/connectors@5.11.2", "", { "dependencies": { "@base-org/account": "1.1.1", "@coinbase/wallet-sdk": "4.3.6", "@gemini-wallet/core": "0.2.0", "@metamask/sdk": "0.33.1", "@safe-global/safe-apps-provider": "0.18.6", "@safe-global/safe-apps-sdk": "9.1.0", "@walletconnect/ethereum-provider": "2.21.1", "cbw-sdk": "npm:@coinbase/wallet-sdk@3.9.3", "porto": "0.2.19" }, "peerDependencies": { "@wagmi/core": "2.21.2", "typescript": ">=5.0.4", "viem": "2.x" }, "optionalPeers": ["typescript"] }, "sha512-OkiElOI8xXGPDZE5UdG6NgDT3laSkEh9llX1DDapUnfnKecK3Tr/HUf5YzgwDhEoox8mdxp+8ZCjtnTKz56SdA=="], - - "@wagmi/core": ["@wagmi/core@2.21.2", "", { "dependencies": { "eventemitter3": "5.0.1", "mipd": "0.0.7", "zustand": "5.0.0" }, "peerDependencies": { "@tanstack/query-core": ">=5.0.0", "typescript": ">=5.0.4", "viem": "2.x" }, "optionalPeers": ["@tanstack/query-core", "typescript"] }, "sha512-Rp4waam2z0FQUDINkJ91jq38PI5wFUHCv1YBL2LXzAQswaEk1ZY8d6+WG3vYGhFHQ22DXy2AlQ8IWmj+2EG3zQ=="], - - "@walletconnect/core": ["@walletconnect/core@2.21.1", "", { "dependencies": { "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/jsonrpc-ws-connection": "1.0.16", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "@walletconnect/relay-api": "1.0.11", "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.21.1", "@walletconnect/utils": "2.21.1", "@walletconnect/window-getters": "1.0.1", "es-toolkit": "1.33.0", "events": "3.3.0", "uint8arrays": "3.1.0" } }, "sha512-Tp4MHJYcdWD846PH//2r+Mu4wz1/ZU/fr9av1UWFiaYQ2t2TPLDiZxjLw54AAEpMqlEHemwCgiRiAmjR1NDdTQ=="], - - "@walletconnect/environment": ["@walletconnect/environment@1.0.1", "", { "dependencies": { "tslib": "1.14.1" } }, "sha512-T426LLZtHj8e8rYnKfzsw1aG6+M0BT1ZxayMdv/p8yM0MU+eJDISqNY3/bccxRr4LrF9csq02Rhqt08Ibl0VRg=="], - - "@walletconnect/ethereum-provider": ["@walletconnect/ethereum-provider@2.21.1", "", { "dependencies": { "@reown/appkit": "1.7.8", "@walletconnect/jsonrpc-http-connection": "1.0.8", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/sign-client": "2.21.1", "@walletconnect/types": "2.21.1", "@walletconnect/universal-provider": "2.21.1", "@walletconnect/utils": "2.21.1", "events": "3.3.0" } }, "sha512-SSlIG6QEVxClgl1s0LMk4xr2wg4eT3Zn/Hb81IocyqNSGfXpjtawWxKxiC5/9Z95f1INyBD6MctJbL/R1oBwIw=="], - - "@walletconnect/events": ["@walletconnect/events@1.0.1", "", { "dependencies": { "keyvaluestorage-interface": "^1.0.0", "tslib": "1.14.1" } }, "sha512-NPTqaoi0oPBVNuLv7qPaJazmGHs5JGyO8eEAk5VGKmJzDR7AHzD4k6ilox5kxk1iwiOnFopBOOMLs86Oa76HpQ=="], - - "@walletconnect/heartbeat": ["@walletconnect/heartbeat@1.2.2", "", { "dependencies": { "@walletconnect/events": "^1.0.1", "@walletconnect/time": "^1.0.2", "events": "^3.3.0" } }, "sha512-uASiRmC5MwhuRuf05vq4AT48Pq8RMi876zV8rr8cV969uTOzWdB/k+Lj5yI2PBtB1bGQisGen7MM1GcZlQTBXw=="], - - "@walletconnect/jsonrpc-http-connection": ["@walletconnect/jsonrpc-http-connection@1.0.8", "", { "dependencies": { "@walletconnect/jsonrpc-utils": "^1.0.6", "@walletconnect/safe-json": "^1.0.1", "cross-fetch": "^3.1.4", "events": "^3.3.0" } }, "sha512-+B7cRuaxijLeFDJUq5hAzNyef3e3tBDIxyaCNmFtjwnod5AGis3RToNqzFU33vpVcxFhofkpE7Cx+5MYejbMGw=="], - - "@walletconnect/jsonrpc-provider": ["@walletconnect/jsonrpc-provider@1.0.14", "", { "dependencies": { "@walletconnect/jsonrpc-utils": "^1.0.8", "@walletconnect/safe-json": "^1.0.2", "events": "^3.3.0" } }, "sha512-rtsNY1XqHvWj0EtITNeuf8PHMvlCLiS3EjQL+WOkxEOA4KPxsohFnBDeyPYiNm4ZvkQdLnece36opYidmtbmow=="], - - "@walletconnect/jsonrpc-types": ["@walletconnect/jsonrpc-types@1.0.4", "", { "dependencies": { "events": "^3.3.0", "keyvaluestorage-interface": "^1.0.0" } }, "sha512-P6679fG/M+wuWg9TY8mh6xFSdYnFyFjwFelxyISxMDrlbXokorEVXYOxiqEbrU3x1BmBoCAJJ+vtEaEoMlpCBQ=="], - - "@walletconnect/jsonrpc-utils": ["@walletconnect/jsonrpc-utils@1.0.8", "", { "dependencies": { "@walletconnect/environment": "^1.0.1", "@walletconnect/jsonrpc-types": "^1.0.3", "tslib": "1.14.1" } }, "sha512-vdeb03bD8VzJUL6ZtzRYsFMq1eZQcM3EAzT0a3st59dyLfJ0wq+tKMpmGH7HlB7waD858UWgfIcudbPFsbzVdw=="], - - "@walletconnect/jsonrpc-ws-connection": ["@walletconnect/jsonrpc-ws-connection@1.0.16", "", { "dependencies": { "@walletconnect/jsonrpc-utils": "^1.0.6", "@walletconnect/safe-json": "^1.0.2", "events": "^3.3.0", "ws": "^7.5.1" } }, "sha512-G81JmsMqh5nJheE1mPst1W0WfVv0SG3N7JggwLLGnI7iuDZJq8cRJvQwLGKHn5H1WTW7DEPCo00zz5w62AbL3Q=="], - - "@walletconnect/keyvaluestorage": ["@walletconnect/keyvaluestorage@1.1.1", "", { "dependencies": { "@walletconnect/safe-json": "^1.0.1", "idb-keyval": "^6.2.1", "unstorage": "^1.9.0" }, "peerDependencies": { "@react-native-async-storage/async-storage": "1.x" }, "optionalPeers": ["@react-native-async-storage/async-storage"] }, "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA=="], - - "@walletconnect/logger": ["@walletconnect/logger@2.1.2", "", { "dependencies": { "@walletconnect/safe-json": "^1.0.2", "pino": "7.11.0" } }, "sha512-aAb28I3S6pYXZHQm5ESB+V6rDqIYfsnHaQyzFbwUUBFY4H0OXx/YtTl8lvhUNhMMfb9UxbwEBS253TlXUYJWSw=="], - - "@walletconnect/relay-api": ["@walletconnect/relay-api@1.0.11", "", { "dependencies": { "@walletconnect/jsonrpc-types": "^1.0.2" } }, "sha512-tLPErkze/HmC9aCmdZOhtVmYZq1wKfWTJtygQHoWtgg722Jd4homo54Cs4ak2RUFUZIGO2RsOpIcWipaua5D5Q=="], - - "@walletconnect/relay-auth": ["@walletconnect/relay-auth@1.1.0", "", { "dependencies": { "@noble/curves": "1.8.0", "@noble/hashes": "1.7.0", "@walletconnect/safe-json": "^1.0.1", "@walletconnect/time": "^1.0.2", "uint8arrays": "^3.0.0" } }, "sha512-qFw+a9uRz26jRCDgL7Q5TA9qYIgcNY8jpJzI1zAWNZ8i7mQjaijRnWFKsCHAU9CyGjvt6RKrRXyFtFOpWTVmCQ=="], - - "@walletconnect/safe-json": ["@walletconnect/safe-json@1.0.2", "", { "dependencies": { "tslib": "1.14.1" } }, "sha512-Ogb7I27kZ3LPC3ibn8ldyUr5544t3/STow9+lzz7Sfo808YD7SBWk7SAsdBFlYgP2zDRy2hS3sKRcuSRM0OTmA=="], - - "@walletconnect/sign-client": ["@walletconnect/sign-client@2.21.1", "", { "dependencies": { "@walletconnect/core": "2.21.1", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.21.1", "@walletconnect/utils": "2.21.1", "events": "3.3.0" } }, "sha512-QaXzmPsMnKGV6tc4UcdnQVNOz4zyXgarvdIQibJ4L3EmLat73r5ZVl4c0cCOcoaV7rgM9Wbphgu5E/7jNcd3Zg=="], - - "@walletconnect/time": ["@walletconnect/time@1.0.2", "", { "dependencies": { "tslib": "1.14.1" } }, "sha512-uzdd9woDcJ1AaBZRhqy5rNC9laqWGErfc4dxA9a87mPdKOgWMD85mcFo9dIYIts/Jwocfwn07EC6EzclKubk/g=="], - - "@walletconnect/types": ["@walletconnect/types@2.21.1", "", { "dependencies": { "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "events": "3.3.0" } }, "sha512-UeefNadqP6IyfwWC1Yi7ux+ljbP2R66PLfDrDm8izmvlPmYlqRerJWJvYO4t0Vvr9wrG4Ko7E0c4M7FaPKT/sQ=="], - - "@walletconnect/universal-provider": ["@walletconnect/universal-provider@2.21.1", "", { "dependencies": { "@walletconnect/events": "1.0.1", "@walletconnect/jsonrpc-http-connection": "1.0.8", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "@walletconnect/sign-client": "2.21.1", "@walletconnect/types": "2.21.1", "@walletconnect/utils": "2.21.1", "es-toolkit": "1.33.0", "events": "3.3.0" } }, "sha512-Wjx9G8gUHVMnYfxtasC9poGm8QMiPCpXpbbLFT+iPoQskDDly8BwueWnqKs4Mx2SdIAWAwuXeZ5ojk5qQOxJJg=="], - - "@walletconnect/utils": ["@walletconnect/utils@2.21.1", "", { "dependencies": { "@noble/ciphers": "1.2.1", "@noble/curves": "1.8.1", "@noble/hashes": "1.7.1", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/relay-api": "1.0.11", "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.21.1", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "bs58": "6.0.0", "detect-browser": "5.3.0", "query-string": "7.1.3", "uint8arrays": "3.1.0", "viem": "2.23.2" } }, "sha512-VPZvTcrNQCkbGOjFRbC24mm/pzbRMUq2DSQoiHlhh0X1U7ZhuIrzVtAoKsrzu6rqjz0EEtGxCr3K1TGRqDG4NA=="], - - "@walletconnect/window-getters": ["@walletconnect/window-getters@1.0.1", "", { "dependencies": { "tslib": "1.14.1" } }, "sha512-vHp+HqzGxORPAN8gY03qnbTMnhqIwjeRJNOMOAzePRg4xVEEE2WvYsI9G2NMjOknA8hnuYbU3/hwLcKbjhc8+Q=="], - - "@walletconnect/window-metadata": ["@walletconnect/window-metadata@1.0.1", "", { "dependencies": { "@walletconnect/window-getters": "^1.0.1", "tslib": "1.14.1" } }, "sha512-9koTqyGrM2cqFRW517BPY/iEtUDx2r1+Pwwu5m7sJ7ka79wi3EyqhqcICk/yDmv6jAS1rjKgTKXlEhanYjijcA=="], - - "abitype": ["abitype@1.1.0", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A=="], - "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], @@ -1192,9 +978,9 @@ "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], - "agents": ["agents@0.1.6", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.18.1", "ai": "5.0.48", "cron-schedule": "^5.0.4", "mimetext": "^3.0.27", "nanoid": "^5.1.6", "partyserver": "^0.0.74", "partysocket": "1.1.5", "x402": "^0.6.1", "zod": "^3.25.76" }, "peerDependencies": { "react": "*" } }, "sha512-sJC8WI4/8W1ac+mQAE3vrsEDwXp5X7J4GFYcZiZIFWfTH4ssoJuBrlx8ikhBSPBFswb5qPPicxzWa0w5LrlT6A=="], + "agents": ["agents@0.2.14", "", { "dependencies": { "@ai-sdk/openai": "2.0.48", "@modelcontextprotocol/sdk": "^1.20.0", "ai": "5.0.68", "cron-schedule": "^5.0.4", "json-schema": "^0.4.0", "json-schema-to-typescript": "^15.0.4", "mimetext": "^3.0.27", "nanoid": "^5.1.6", "partyserver": "^0.0.75", "partysocket": "1.1.6", "zod": "^3.25.76", "zod-to-ts": "^1.2.0" }, "peerDependencies": { "react": "*", "viem": ">=2.0.0", "x402": "^0.6.5" }, "optionalPeers": ["viem", "x402"] }, "sha512-DjMZomVmB11DCBglkeVyeHhb9FpdEfdtXT6+ZPpda7HT0yQxz1zDu6scKSpyodTu1sS7O3tA7xo4F1mvW+7M+Q=="], - "ai": ["ai@5.0.48", "", { "dependencies": { "@ai-sdk/gateway": "1.0.25", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.9", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-+oYhbN3NGRXayGfTFI8k1Fu4rhiJcQ0mbgiAOJGFkzvCxunRRQu5cyDl7y6cHNTj1QvHmIBROK5u655Ss2oI0g=="], + "ai": ["ai@5.0.68", "", { "dependencies": { "@ai-sdk/gateway": "1.0.39", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.12", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-SB6r+4TkKVlSg2ozGBSfuf6Is5hrcX/bpGBzOoyHIN3b4ILGhaly0IHEvP8+3GGIHXqtkPVEUmR6V05jKdjNlg=="], "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], @@ -1232,12 +1018,8 @@ "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], - "async-mutex": ["async-mutex@0.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw=="], - "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], - "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], - "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], "babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="], @@ -1254,16 +1036,12 @@ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "base-x": ["base-x@5.0.1", "", {}, "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg=="], - "base64-arraybuffer": ["base64-arraybuffer@1.0.2", "", {}, "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="], "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], - "big.js": ["big.js@6.2.2", "", {}, "sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ=="], - "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], "birpc": ["birpc@0.2.14", "", {}, "sha512-37FHE8rqsYM5JEKCnXFyHpBCzvgHEExwVVTq+nUmloInU7l8ezD1TpOhKpS8oe1DTYFqEK27rFZVKG43oTqXRA=="], @@ -1274,8 +1052,6 @@ "body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="], - "bowser": ["bowser@2.12.1", "", {}, "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw=="], - "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -1300,8 +1076,6 @@ "bs-logger": ["bs-logger@0.2.6", "", { "dependencies": { "fast-json-stable-stringify": "2.x" } }, "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog=="], - "bs58": ["bs58@6.0.0", "", { "dependencies": { "base-x": "^5.0.0" } }, "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw=="], - "bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="], "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], @@ -1330,8 +1104,6 @@ "caniuse-lite": ["caniuse-lite@1.0.30001741", "", {}, "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw=="], - "cbw-sdk": ["@coinbase/wallet-sdk@3.9.3", "", { "dependencies": { "bn.js": "^5.2.1", "buffer": "^6.0.3", "clsx": "^1.2.1", "eth-block-tracker": "^7.1.0", "eth-json-rpc-filters": "^6.0.0", "eventemitter3": "^5.0.1", "keccak": "^3.0.3", "preact": "^10.16.0", "sha.js": "^2.4.11" } }, "sha512-N/A2DRIf0Y3PHc1XAMvbBUu4zisna6qAdqABMZwBMNEfWrXpAwx16pZGkYCLGE+Rvv1edbcB2LYDRnACNcmCiw=="], - "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], @@ -1386,8 +1158,6 @@ "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], - "commander": ["commander@14.0.1", "", {}, "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A=="], - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], "console-browserify": ["console-browserify@1.2.0", "", {}, "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA=="], @@ -1402,8 +1172,6 @@ "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], - "cookie-es": ["cookie-es@1.2.2", "", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="], - "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], "core-js-pure": ["core-js-pure@3.45.1", "", {}, "sha512-OHnWFKgTUshEU8MK+lOs1H8kC8GkTi9Z1tvNkxrCcw9wl3MJIO7q2ld77wjWn4/xuGrVu2X+nME1iIIPBSdyEQ=="], @@ -1414,8 +1182,6 @@ "cosmiconfig": ["cosmiconfig@8.3.6", "", { "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA=="], - "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], - "create-ecdh": ["create-ecdh@4.0.4", "", { "dependencies": { "bn.js": "^4.1.0", "elliptic": "^6.5.3" } }, "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A=="], "create-hash": ["create-hash@1.2.0", "", { "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", "md5.js": "^1.3.4", "ripemd160": "^2.0.1", "sha.js": "^2.4.0" } }, "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg=="], @@ -1428,12 +1194,8 @@ "cron-schedule": ["cron-schedule@5.0.4", "", {}, "sha512-nH0a49E/kSVk6BeFgKZy4uUsy6D2A16p120h5bYD9ILBhQu7o2sJFH+WI4R731TSBQ0dB1Ik7inB/dRAB4C8QQ=="], - "cross-fetch": ["cross-fetch@4.1.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw=="], - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - "crossws": ["crossws@0.3.5", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="], - "crypto-browserify": ["crypto-browserify@3.12.1", "", { "dependencies": { "browserify-cipher": "^1.0.1", "browserify-sign": "^4.2.3", "create-ecdh": "^4.0.4", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", "diffie-hellman": "^5.0.3", "hash-base": "~3.0.4", "inherits": "^2.0.4", "pbkdf2": "^3.1.2", "public-encrypt": "^4.0.3", "randombytes": "^2.1.0", "randomfill": "^1.0.4" } }, "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ=="], "css-line-break": ["css-line-break@2.1.0", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w=="], @@ -1476,18 +1238,12 @@ "date-fns-jalali": ["date-fns-jalali@4.1.0-0", "", {}, "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg=="], - "dayjs": ["dayjs@1.11.13", "", {}, "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="], - "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], - "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], - "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], - "decode-uri-component": ["decode-uri-component@0.2.2", "", {}, "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="], - "dedent": ["dedent@1.7.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ=="], "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], @@ -1508,14 +1264,8 @@ "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], - "derive-valtio": ["derive-valtio@0.1.0", "", { "peerDependencies": { "valtio": "*" } }, "sha512-OCg2UsLbXK7GmmpzMXhYkdO64vhJ1ROUUGaTFyHjVwEdMEcTTRj7W1TxLbSBxdY8QLBPCcp66MTyaSy0RpO17A=="], - "des.js": ["des.js@1.1.0", "", { "dependencies": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg=="], - "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], - - "detect-browser": ["detect-browser@5.3.0", "", {}, "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w=="], - "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], "detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="], @@ -1532,8 +1282,6 @@ "diffie-hellman": ["diffie-hellman@5.0.3", "", { "dependencies": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" } }, "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg=="], - "dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="], - "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], "domain-browser": ["domain-browser@4.22.0", "", {}, "sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw=="], @@ -1548,12 +1296,8 @@ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - "duplexify": ["duplexify@4.1.3", "", { "dependencies": { "end-of-stream": "^1.4.1", "inherits": "^2.0.3", "readable-stream": "^3.1.1", "stream-shift": "^1.0.2" } }, "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA=="], - "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - "eciesjs": ["eciesjs@0.4.15", "", { "dependencies": { "@ecies/ciphers": "^0.2.3", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.1", "@noble/hashes": "^1.8.0" } }, "sha512-r6kEJXDKecVOCj2nLMuXK/FCPeurW33+3JRpfXVbjLja3XUYFfD9I/JBreH6sUyzcm3G/YQboBjMla6poKeSdA=="], - "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], "electron-to-chromium": ["electron-to-chromium@1.5.217", "", {}, "sha512-Pludfu5iBxp9XzNl0qq2G87hdD17ZV7h5T4n6rQXDi3nCyloBV3jreE9+8GC6g4X/5yxqVgXEURpcLtM0WS4jA=="], @@ -1570,16 +1314,8 @@ "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - "encode-utf8": ["encode-utf8@1.0.3", "", {}, "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw=="], - "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], - "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], - - "engine.io-client": ["engine.io-client@6.6.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", "ws": "~8.17.1", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w=="], - - "engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="], - "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], @@ -1654,22 +1390,10 @@ "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], - "eth-block-tracker": ["eth-block-tracker@7.1.0", "", { "dependencies": { "@metamask/eth-json-rpc-provider": "^1.0.0", "@metamask/safe-event-emitter": "^3.0.0", "@metamask/utils": "^5.0.1", "json-rpc-random-id": "^1.0.1", "pify": "^3.0.0" } }, "sha512-8YdplnuE1IK4xfqpf4iU7oBxnOYAc35934o083G8ao+8WM8QQtt/mVlAY6yIAdY1eMeLqg4Z//PZjJGmWGPMRg=="], - - "eth-json-rpc-filters": ["eth-json-rpc-filters@6.0.1", "", { "dependencies": { "@metamask/safe-event-emitter": "^3.0.0", "async-mutex": "^0.2.6", "eth-query": "^2.1.2", "json-rpc-engine": "^6.1.0", "pify": "^5.0.0" } }, "sha512-ITJTvqoCw6OVMLs7pI8f4gG92n/St6x80ACtHodeS+IXmO0w+t1T5OOzfSt7KLSMLRkVUoexV7tztLgDxg+iig=="], - - "eth-query": ["eth-query@2.1.2", "", { "dependencies": { "json-rpc-random-id": "^1.0.0", "xtend": "^4.0.1" } }, "sha512-srES0ZcvwkR/wd5OQBRA1bIJMww1skfGS0s8wlwK3/oNP4+wnds60krvu5R1QbpRQjMmpG5OMIWro5s7gvDPsA=="], - - "eth-rpc-errors": ["eth-rpc-errors@4.0.3", "", { "dependencies": { "fast-safe-stringify": "^2.0.6" } }, "sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg=="], - - "ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="], - "event-target-polyfill": ["event-target-polyfill@0.0.4", "", {}, "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ=="], "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], - "eventemitter2": ["eventemitter2@6.4.9", "", {}, "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg=="], - "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], @@ -1698,8 +1422,6 @@ "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], - "extension-port-stream": ["extension-port-stream@3.0.0", "", { "dependencies": { "readable-stream": "^3.6.2 || ^4.4.2", "webextension-polyfill": ">=0.10.0 <1.0" } }, "sha512-an2S5quJMiy5bnZKEf6AkfH/7r8CzHvhchU40gxN+OM6HPhe7Z9T1FUychcf2M9PpPOO0Hf7BAEfJkw2TDIBDw=="], - "fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -1710,12 +1432,6 @@ "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - "fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="], - - "fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="], - - "fastestsmallesttextencoderdecoder": ["fastestsmallesttextencoderdecoder@1.0.22", "", {}, "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw=="], - "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], "fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="], @@ -1732,8 +1448,6 @@ "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - "filter-obj": ["filter-obj@1.1.0", "", {}, "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="], - "finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="], "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], @@ -1808,8 +1522,6 @@ "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], - "h3": ["h3@1.15.4", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.2", "radix3": "^1.1.2", "ufo": "^1.6.1", "uncrypto": "^0.1.3" } }, "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ=="], - "handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="], "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], @@ -1860,8 +1572,6 @@ "iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], - "idb-keyval": ["idb-keyval@6.2.1", "", {}, "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg=="], - "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], @@ -1888,8 +1598,6 @@ "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], - "iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="], - "is-absolute-url": ["is-absolute-url@4.0.1", "", {}, "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A=="], "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], @@ -1976,8 +1684,6 @@ "isomorphic-timers-promises": ["isomorphic-timers-promises@1.0.1", "", {}, "sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ=="], - "isows": ["isows@1.0.7", "", { "peerDependencies": { "ws": "*" } }, "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg=="], - "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], "istanbul-lib-instrument": ["istanbul-lib-instrument@6.0.3", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } }, "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q=="], @@ -2058,12 +1764,10 @@ "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], - "json-rpc-engine": ["json-rpc-engine@6.1.0", "", { "dependencies": { "@metamask/safe-event-emitter": "^2.0.0", "eth-rpc-errors": "^4.0.2" } }, "sha512-NEdLrtrq1jUZyfjkr9OCz9EzCNhnRyWtt1PAnvnhwy6e8XETS0Dtc+ZNCO2gvuAoKsIn2+vCSowXTYE4CkgnAQ=="], - - "json-rpc-random-id": ["json-rpc-random-id@1.0.1", "", {}, "sha512-RJ9YYNCkhVDBuP4zN5BBtYAzEl03yq/jIIsyif0JY9qyJuQQZNeDK7anAPKKlyEtLSj2s8h6hNh2F8zO5q7ScA=="], - "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], + "json-schema-to-typescript": ["json-schema-to-typescript@15.0.4", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.5.5", "@types/json-schema": "^7.0.15", "@types/lodash": "^4.17.7", "is-glob": "^4.0.3", "js-yaml": "^4.1.0", "lodash": "^4.17.21", "minimist": "^1.2.8", "prettier": "^3.2.5", "tinyglobby": "^0.2.9" }, "bin": { "json2ts": "dist/src/cli.js" } }, "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ=="], + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], @@ -2072,12 +1776,8 @@ "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], - "keccak": ["keccak@3.0.4", "", { "dependencies": { "node-addon-api": "^2.0.0", "node-gyp-build": "^4.2.0", "readable-stream": "^3.6.0" } }, "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q=="], - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - "keyvaluestorage-interface": ["keyvaluestorage-interface@1.0.0", "", {}, "sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g=="], - "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], "knip": ["knip@5.64.1", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "jiti": "^2.6.0", "js-yaml": "^4.1.0", "minimist": "^1.2.8", "oxc-resolver": "^11.8.3", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.4.1", "strip-json-comments": "5.0.2", "zod": "^4.1.11" }, "peerDependencies": { "@types/node": ">=18", "typescript": ">=5.0.4 <7" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-80XnLsyeXuyxj1F4+NBtQFHxaRH0xWRw8EKwfQ6EkVZZ0bSz/kqqan08k/Qg8ajWsFPhFq+0S2RbLCBGIQtuOg=="], @@ -2112,12 +1812,6 @@ "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - "lit": ["lit@3.3.0", "", { "dependencies": { "@lit/reactive-element": "^2.1.0", "lit-element": "^4.2.0", "lit-html": "^3.3.0" } }, "sha512-DGVsqsOIHBww2DqnuZzW7QsuCdahp50ojuDaBPC7jUDRpYoH0z7kHBBYZewRzer75FwtrkmkKk7iOAwSaWdBmw=="], - - "lit-element": ["lit-element@4.2.1", "", { "dependencies": { "@lit-labs/ssr-dom-shim": "^1.4.0", "@lit/reactive-element": "^2.1.0", "lit-html": "^3.3.0" } }, "sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw=="], - - "lit-html": ["lit-html@3.3.1", "", { "dependencies": { "@types/trusted-types": "^2.0.2" } }, "sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA=="], - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], @@ -2190,8 +1884,6 @@ "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - "micro-ftch": ["micro-ftch@0.3.1", "", {}, "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg=="], - "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], @@ -2276,8 +1968,6 @@ "minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="], - "mipd": ["mipd@0.0.7", "", { "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg=="], - "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], "monaco-editor": ["monaco-editor@0.52.2", "", {}, "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ=="], @@ -2288,8 +1978,6 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], - "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], "napi-postinstall": ["napi-postinstall@0.3.3", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow=="], @@ -2304,20 +1992,14 @@ "no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="], - "node-addon-api": ["node-addon-api@2.0.2", "", {}, "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA=="], - "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], - "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], - "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], - "node-mock-http": ["node-mock-http@1.0.3", "", {}, "sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog=="], - "node-releases": ["node-releases@2.0.20", "", {}, "sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA=="], "node-stdlib-browser": ["node-stdlib-browser@1.3.1", "", { "dependencies": { "assert": "^2.0.0", "browser-resolve": "^2.0.0", "browserify-zlib": "^0.2.0", "buffer": "^5.7.1", "console-browserify": "^1.1.0", "constants-browserify": "^1.0.0", "create-require": "^1.1.1", "crypto-browserify": "^3.12.1", "domain-browser": "4.22.0", "events": "^3.0.0", "https-browserify": "^1.0.0", "isomorphic-timers-promises": "^1.0.1", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "pkg-dir": "^5.0.0", "process": "^0.11.10", "punycode": "^1.4.1", "querystring-es3": "^0.2.1", "readable-stream": "^3.6.0", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "string_decoder": "^1.0.0", "timers-browserify": "^2.0.4", "tty-browserify": "0.0.1", "url": "^0.11.4", "util": "^0.12.4", "vm-browserify": "^1.0.1" } }, "sha512-X75ZN8DCLftGM5iKwoYLA3rjnrAEs97MkzvSd4q2746Tgpg8b8XWiBGiBG4ZpgcAqBgtgPHTiAc8ZMCvZuikDw=="], @@ -2328,8 +2010,6 @@ "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], - "obj-multiplex": ["obj-multiplex@1.0.0", "", { "dependencies": { "end-of-stream": "^1.4.0", "once": "^1.4.0", "readable-stream": "^2.3.3" } }, "sha512-0GNJAOsHoBHeNTvl5Vt6IWnpUEcc3uSRxzBri7EDyIcMgYvnY2JL2qdeV5zTMjWQX5OHcD5amcW2HFfDh0gjIA=="], - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], @@ -2346,12 +2026,8 @@ "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], - "ofetch": ["ofetch@1.4.1", "", { "dependencies": { "destr": "^2.0.3", "node-fetch-native": "^1.6.4", "ufo": "^1.5.4" } }, "sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw=="], - "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], - "on-exit-leak-free": ["on-exit-leak-free@0.2.0", "", {}, "sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg=="], - "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], @@ -2360,18 +2036,12 @@ "openai": ["openai@5.23.1", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-APxMtm5mln4jhKhAr0d5zP9lNsClx4QwJtg8RUvYSSyxYCTHLNJnLEcSHbJ6t0ori8Pbr9HZGfcPJ7LEy73rvQ=="], - "openapi-fetch": ["openapi-fetch@0.13.8", "", { "dependencies": { "openapi-typescript-helpers": "^0.0.15" } }, "sha512-yJ4QKRyNxE44baQ9mY5+r/kAzZ8yXMemtNAOFwOzRXJscdjSxxzWSNlyBAr+o5JjkUw9Lc3W7OIoca0cY3PYnQ=="], - - "openapi-typescript-helpers": ["openapi-typescript-helpers@0.0.15", "", {}, "sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw=="], - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "os-browserify": ["os-browserify@0.3.0", "", {}, "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A=="], "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], - "ox": ["ox@0.9.6", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.0.9", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg=="], - "oxc-resolver": ["oxc-resolver@11.8.3", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.8.3", "@oxc-resolver/binding-android-arm64": "11.8.3", "@oxc-resolver/binding-darwin-arm64": "11.8.3", "@oxc-resolver/binding-darwin-x64": "11.8.3", "@oxc-resolver/binding-freebsd-x64": "11.8.3", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.8.3", "@oxc-resolver/binding-linux-arm-musleabihf": "11.8.3", "@oxc-resolver/binding-linux-arm64-gnu": "11.8.3", "@oxc-resolver/binding-linux-arm64-musl": "11.8.3", "@oxc-resolver/binding-linux-ppc64-gnu": "11.8.3", "@oxc-resolver/binding-linux-riscv64-gnu": "11.8.3", "@oxc-resolver/binding-linux-riscv64-musl": "11.8.3", "@oxc-resolver/binding-linux-s390x-gnu": "11.8.3", "@oxc-resolver/binding-linux-x64-gnu": "11.8.3", "@oxc-resolver/binding-linux-x64-musl": "11.8.3", "@oxc-resolver/binding-wasm32-wasi": "11.8.3", "@oxc-resolver/binding-win32-arm64-msvc": "11.8.3", "@oxc-resolver/binding-win32-ia32-msvc": "11.8.3", "@oxc-resolver/binding-win32-x64-msvc": "11.8.3" } }, "sha512-wPY3eiw24QOiNqArh5FWRrKYr1Yuxya8bE8CV7yBfz7jodCrg0HqBu6cvHHSicxFug7D4TN6ox1hysA9arHTqw=="], "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], @@ -2394,7 +2064,7 @@ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], - "partyserver": ["partyserver@0.0.74", "", { "dependencies": { "nanoid": "^5.1.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20240729.0" } }, "sha512-5cx+Hpg8UWFh/S6azhbnvrTAfM0k+/NxEfyaLwHdqQEzvYVFmLkLCZNbolyc0NKfDhU27a0itEJfz0jF8cFuFw=="], + "partyserver": ["partyserver@0.0.75", "", { "dependencies": { "nanoid": "^5.1.6" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20240729.0" } }, "sha512-i/18vvdxuGjx+rpQ+fDdExlvQoRb7EfTF+6b+kA2ILEpHemtpLWV8NdgDrOPEklRNdCc/4WlzDtYn05d17aZAQ=="], "partysocket": ["partysocket@1.1.5", "", { "dependencies": { "event-target-polyfill": "^0.0.4" } }, "sha512-8uw9foq9bij4sKLCtTSHvyqMrMTQ5FJjrHc7BjoM2s95Vu7xYCN63ABpI7OZHC7ZMP5xaom/A+SsoFPXmTV6ZQ=="], @@ -2426,34 +2096,18 @@ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - "pify": ["pify@3.0.0", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="], - - "pino": ["pino@7.11.0", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.0.0", "on-exit-leak-free": "^0.2.0", "pino-abstract-transport": "v0.5.0", "pino-std-serializers": "^4.0.0", "process-warning": "^1.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.1.0", "safe-stable-stringify": "^2.1.0", "sonic-boom": "^2.2.1", "thread-stream": "^0.15.1" }, "bin": { "pino": "bin.js" } }, "sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg=="], - - "pino-abstract-transport": ["pino-abstract-transport@0.5.0", "", { "dependencies": { "duplexify": "^4.1.2", "split2": "^4.0.0" } }, "sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ=="], - - "pino-std-serializers": ["pino-std-serializers@4.0.0", "", {}, "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q=="], - "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], "pkce-challenge": ["pkce-challenge@5.0.0", "", {}, "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ=="], "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], - "pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="], - - "pony-cause": ["pony-cause@2.1.11", "", {}, "sha512-M7LhCsdNbNgiLYiP4WjsfLUuFmCfnjdF6jKe2R9NKl4WFN+HZPGHJZ9lnLP7f9ZnKe3U9nuWD0szirmj+migUg=="], - - "porto": ["porto@0.2.19", "", { "dependencies": { "hono": "^4.9.6", "idb-keyval": "^6.2.1", "mipd": "^0.0.7", "ox": "^0.9.6", "zod": "^4.1.5", "zustand": "^5.0.1" }, "peerDependencies": { "@tanstack/react-query": ">=5.59.0", "@wagmi/core": ">=2.16.3", "react": ">=18", "typescript": ">=5.4.0", "viem": ">=2.37.0", "wagmi": ">=2.0.0" }, "optionalPeers": ["@tanstack/react-query", "react", "typescript", "wagmi"], "bin": { "porto": "_dist/cli/bin/index.js" } }, "sha512-q1vEJgdtlEOf6byWgD31GHiMwpfLuxFSfx9f7Sw4RGdvpQs2ANBGfnzzardADZegr87ZXsebSp+3vaaznEUzPQ=="], - "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], "postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="], - "preact": ["preact@10.24.2", "", {}, "sha512-1cSoF0aCC8uaARATfrlz4VCBqE8LwZwRfLgkxJOQwAlQt6ayTmi0D9OF7nXid1POI5SZidFuG9CnlXbDfLqY/Q=="], - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], @@ -2466,8 +2120,6 @@ "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], - "process-warning": ["process-warning@1.0.0", "", {}, "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q=="], - "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], @@ -2478,32 +2130,20 @@ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], - "proxy-compare": ["proxy-compare@2.6.0", "", {}, "sha512-8xuCeM3l8yqdmbPoYeLbrAXCBWu19XEYc5/F28f5qOaoAIMyfmBUkl5axiK+x9olUvRlcekvnm98AP9RDngOIw=="], - "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], "public-encrypt": ["public-encrypt@4.0.3", "", { "dependencies": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", "create-hash": "^1.1.0", "parse-asn1": "^5.0.0", "randombytes": "^2.0.1", "safe-buffer": "^5.1.2" } }, "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q=="], - "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], - "punycode": ["punycode@1.4.1", "", {}, "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="], "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], - "qrcode": ["qrcode@1.5.3", "", { "dependencies": { "dijkstrajs": "^1.0.1", "encode-utf8": "^1.0.3", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg=="], - "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], - "query-string": ["query-string@7.1.3", "", { "dependencies": { "decode-uri-component": "^0.2.2", "filter-obj": "^1.1.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" } }, "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg=="], - "querystring-es3": ["querystring-es3@0.2.1", "", {}, "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA=="], "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], - - "radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="], - "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], "randomfill": ["randomfill@1.0.4", "", { "dependencies": { "randombytes": "^2.0.5", "safe-buffer": "^5.1.0" } }, "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw=="], @@ -2544,8 +2184,6 @@ "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - "real-require": ["real-require@0.1.0", "", {}, "sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg=="], - "recharts": ["recharts@3.2.1", "", { "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw=="], "redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="], @@ -2568,8 +2206,6 @@ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], - "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="], - "reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="], "resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], @@ -2600,8 +2236,6 @@ "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], - "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], - "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], @@ -2612,8 +2246,6 @@ "serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="], - "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], - "set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="], "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], @@ -2656,12 +2288,6 @@ "snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="], - "socket.io-client": ["socket.io-client@4.8.1", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ=="], - - "socket.io-parser": ["socket.io-parser@4.2.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" } }, "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew=="], - - "sonic-boom": ["sonic-boom@2.8.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg=="], - "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], @@ -2672,10 +2298,6 @@ "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], - "split-on-first": ["split-on-first@1.1.0", "", {}, "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="], - - "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], - "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], "stable-hash-x": ["stable-hash-x@0.2.0", "", {}, "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ=="], @@ -2696,10 +2318,6 @@ "stream-http": ["stream-http@3.2.0", "", { "dependencies": { "builtin-status-codes": "^3.0.0", "inherits": "^2.0.4", "readable-stream": "^3.6.0", "xtend": "^4.0.2" } }, "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A=="], - "stream-shift": ["stream-shift@1.0.3", "", {}, "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ=="], - - "strict-uri-encode": ["strict-uri-encode@2.0.0", "", {}, "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="], - "string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="], "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], @@ -2734,8 +2352,6 @@ "suffix-array": ["suffix-array@0.1.4", "", {}, "sha512-oNhTjdKDf8SdyUWm/igHHmdDc/qwxmxHX0TrfUHgGAwDXICnzBG4PW0O0hGSGGW9/HS+TXhjxF3C/aeajMdl8A=="], - "superstruct": ["superstruct@1.0.4", "", {}, "sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ=="], - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], @@ -2754,8 +2370,6 @@ "text-segmentation": ["text-segmentation@1.0.3", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw=="], - "thread-stream": ["thread-stream@0.15.2", "", { "dependencies": { "real-require": "^0.1.0" } }, "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA=="], - "timers-browserify": ["timers-browserify@2.0.12", "", { "dependencies": { "setimmediate": "^1.0.4" } }, "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ=="], "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], @@ -2824,12 +2438,8 @@ "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], - "uint8arrays": ["uint8arrays@3.1.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-ei5rfKtoRO8OyOIor2Rz5fhzjThwIHJZ3uyDPnDHTXbP0aMQ1RN/6AI5B5d9dBxJOU+BvOAk7ZQ1xphsX8Lrog=="], - "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], - "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], - "undici": ["undici@7.14.0", "", {}, "sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], @@ -2856,8 +2466,6 @@ "unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="], - "unstorage": ["unstorage@1.17.1", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", "destr": "^2.0.5", "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", "ofetch": "^1.4.1", "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-KKGwRTT0iVBCErKemkJCLs7JdxNVfqTPc/85ae1XES0+bsHbc/sFBfVi5kJp156cc51BHinIH2l3k0EZ24vOBQ=="], - "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], @@ -2878,12 +2486,8 @@ "utrie": ["utrie@1.0.2", "", { "dependencies": { "base64-arraybuffer": "^1.0.2" } }, "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw=="], - "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], - "v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="], - "valtio": ["valtio@1.13.2", "", { "dependencies": { "derive-valtio": "0.1.0", "proxy-compare": "2.6.0", "use-sync-external-store": "1.2.0" }, "peerDependencies": { "@types/react": ">=16.8", "react": ">=16.8" }, "optionalPeers": ["@types/react", "react"] }, "sha512-Qik0o+DSy741TmkqmRfjq+0xpZBXi/Y6+fXZLn0xNF1z/waFMbE3rkivv5Zcf9RrMUp6zswf2J7sbh2KBlba5A=="], - "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], "vaul": ["vaul@1.1.2", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA=="], @@ -2894,8 +2498,6 @@ "victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="], - "viem": ["viem@2.37.8", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.1.0", "isows": "1.0.7", "ox": "0.9.6", "ws": "8.18.3" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-mL+5yvCQbRIR6QvngDQMfEiZTfNWfd+/QL5yFaOoYbpH3b1Q2ddwF7YG2eI2AcYSh9LE1gtUkbzZLFUAVyj4oQ=="], - "vite": ["rolldown-vite@7.1.13", "", { "dependencies": { "@oxc-project/runtime": "0.92.0", "fdir": "^6.5.0", "lightningcss": "^1.30.1", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-beta.40", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.25.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-wYRnqlO+nKcvZitHjwXCnGy+xaFW8mBWL6zScZWJK/ZtEs9Be4ngabaDN05l7t+xFgSzZbPYbWdORBVTfWm7uA=="], "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], @@ -2910,16 +2512,12 @@ "vm-browserify": ["vm-browserify@1.1.2", "", {}, "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="], - "wagmi": ["wagmi@2.17.5", "", { "dependencies": { "@wagmi/connectors": "5.11.2", "@wagmi/core": "2.21.2", "use-sync-external-store": "1.4.0" }, "peerDependencies": { "@tanstack/react-query": ">=5.0.0", "react": ">=18", "typescript": ">=5.0.4", "viem": "2.x" }, "optionalPeers": ["typescript"] }, "sha512-Sk2e40gfo68gbJ6lHkpIwCMkH76rO0+toCPjf3PzdQX37rZo9042DdNTYcSg3zhnx8abFJtrk/5vAWfR8APTDw=="], - "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="], "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], - "webextension-polyfill": ["webextension-polyfill@0.10.0", "", {}, "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g=="], - "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], "webpack-sources": ["webpack-sources@3.3.3", "", {}, "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg=="], @@ -2936,8 +2534,6 @@ "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="], - "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="], - "which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="], "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], @@ -2960,10 +2556,6 @@ "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], - "x402": ["x402@0.6.5", "", { "dependencies": { "@scure/base": "^1.2.6", "@solana-program/compute-budget": "^0.8.0", "@solana-program/token": "^0.5.1", "@solana-program/token-2022": "^0.4.2", "@solana/kit": "^2.1.1", "@solana/transaction-confirmation": "^2.1.1", "viem": "^2.21.26", "wagmi": "^2.15.6", "zod": "^3.24.2" } }, "sha512-Pk+JZiCwuXSzwuhY6Dsz5mu/O9T6QnZD7VPVcgI3gixe4F/rwv4h09bUygB9kckRpq+XYYx3Hqv4nTk8uBCzvQ=="], - - "xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="], - "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], @@ -2984,7 +2576,7 @@ "zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="], - "zustand": ["zustand@5.0.0", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ=="], + "zod-to-ts": ["zod-to-ts@1.2.0", "", { "peerDependencies": { "typescript": "^4.9.4 || ^5.0.2", "zod": "^3" } }, "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], @@ -2996,28 +2588,12 @@ "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@base-org/account/@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="], - - "@base-org/account/clsx": ["clsx@1.2.1", "", {}, "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="], - - "@base-org/account/ox": ["ox@0.6.9", "", { "dependencies": { "@adraffy/ens-normalize": "^1.10.1", "@noble/curves": "^1.6.0", "@noble/hashes": "^1.5.0", "@scure/bip32": "^1.5.0", "@scure/bip39": "^1.4.0", "abitype": "^1.0.6", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug=="], - - "@base-org/account/zustand": ["zustand@5.0.3", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg=="], - "@cloudflare/vite-plugin/wrangler": ["wrangler@4.41.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.5", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251001.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.21", "workerd": "1.20251001.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251001.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-SPiBk/5SgCSIdcWw8EXc8DzqtrjbIU+/n22fQjyz4RnULAqCFJjy84F5crcWnb1J/iPiOzm7mS9bMGFFtpwS/w=="], "@cloudflare/vitest-pool-workers/miniflare": ["miniflare@4.20250906.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "^7.10.0", "workerd": "1.20250906.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-T/RWn1sa0ien80s6NjU+Un/tj12gR6wqScZoiLeMJDD4/fK0UXfnbWXJDubnUED8Xjm7RPQ5ESYdE+mhPmMtuQ=="], "@cloudflare/vitest-pool-workers/wrangler": ["wrangler@4.35.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.3", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20250906.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.21", "workerd": "1.20250906.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250906.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-HbyXtbrh4Fi3mU8ussY85tVdQ74qpVS1vctUgaPc+bPrXBTqfDLkZ6VRtHAVF/eBhz4SFmhJtCQpN1caY2Ak8A=="], - "@coinbase/wallet-sdk/@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="], - - "@coinbase/wallet-sdk/clsx": ["clsx@1.2.1", "", {}, "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="], - - "@coinbase/wallet-sdk/ox": ["ox@0.6.9", "", { "dependencies": { "@adraffy/ens-normalize": "^1.10.1", "@noble/curves": "^1.6.0", "@noble/hashes": "^1.5.0", "@scure/bip32": "^1.5.0", "@scure/bip39": "^1.4.0", "abitype": "^1.0.6", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug=="], - - "@coinbase/wallet-sdk/zustand": ["zustand@5.0.3", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg=="], - "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], @@ -3062,42 +2638,8 @@ "@jest/types/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "@metamask/eth-json-rpc-provider/@metamask/json-rpc-engine": ["@metamask/json-rpc-engine@7.3.3", "", { "dependencies": { "@metamask/rpc-errors": "^6.2.1", "@metamask/safe-event-emitter": "^3.0.0", "@metamask/utils": "^8.3.0" } }, "sha512-dwZPq8wx9yV3IX2caLi9q9xZBw2XeIoYqdyihDDDpuHVCEiqadJLwqM3zy+uwf6F1QYQ65A8aOMQg1Uw7LMLNg=="], - - "@metamask/eth-json-rpc-provider/@metamask/utils": ["@metamask/utils@5.0.2", "", { "dependencies": { "@ethereumjs/tx": "^4.1.2", "@types/debug": "^4.1.7", "debug": "^4.3.4", "semver": "^7.3.8", "superstruct": "^1.0.3" } }, "sha512-yfmE79bRQtnMzarnKfX7AEJBwFTxvTyw3nBQlu/5rmGXrjAeAMltoGxO62TFurxrQAFMNa/fEjIHNvungZp0+g=="], - - "@metamask/json-rpc-engine/@metamask/rpc-errors": ["@metamask/rpc-errors@6.4.0", "", { "dependencies": { "@metamask/utils": "^9.0.0", "fast-safe-stringify": "^2.0.6" } }, "sha512-1ugFO1UoirU2esS3juZanS/Fo8C8XYocCuBpfZI5N7ECtoG+zu0wF+uWZASik6CkO6w9n/Iebt4iI4pT0vptpg=="], - - "@metamask/json-rpc-engine/@metamask/utils": ["@metamask/utils@8.5.0", "", { "dependencies": { "@ethereumjs/tx": "^4.2.0", "@metamask/superstruct": "^3.0.0", "@noble/hashes": "^1.3.1", "@scure/base": "^1.1.3", "@types/debug": "^4.1.7", "debug": "^4.3.4", "pony-cause": "^2.1.10", "semver": "^7.5.4", "uuid": "^9.0.1" } }, "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ=="], - - "@metamask/json-rpc-middleware-stream/@metamask/utils": ["@metamask/utils@8.5.0", "", { "dependencies": { "@ethereumjs/tx": "^4.2.0", "@metamask/superstruct": "^3.0.0", "@noble/hashes": "^1.3.1", "@scure/base": "^1.1.3", "@types/debug": "^4.1.7", "debug": "^4.3.4", "pony-cause": "^2.1.10", "semver": "^7.5.4", "uuid": "^9.0.1" } }, "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ=="], - - "@metamask/providers/@metamask/rpc-errors": ["@metamask/rpc-errors@6.4.0", "", { "dependencies": { "@metamask/utils": "^9.0.0", "fast-safe-stringify": "^2.0.6" } }, "sha512-1ugFO1UoirU2esS3juZanS/Fo8C8XYocCuBpfZI5N7ECtoG+zu0wF+uWZASik6CkO6w9n/Iebt4iI4pT0vptpg=="], - - "@metamask/providers/@metamask/utils": ["@metamask/utils@8.5.0", "", { "dependencies": { "@ethereumjs/tx": "^4.2.0", "@metamask/superstruct": "^3.0.0", "@noble/hashes": "^1.3.1", "@scure/base": "^1.1.3", "@types/debug": "^4.1.7", "debug": "^4.3.4", "pony-cause": "^2.1.10", "semver": "^7.5.4", "uuid": "^9.0.1" } }, "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ=="], - - "@metamask/sdk/debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], - - "@metamask/sdk-communication-layer/date-fns": ["date-fns@2.30.0", "", { "dependencies": { "@babel/runtime": "^7.21.0" } }, "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw=="], - - "@metamask/sdk-communication-layer/debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], - - "@metamask/utils/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], - "@reown/appkit/@walletconnect/types": ["@walletconnect/types@2.21.0", "", { "dependencies": { "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "events": "3.3.0" } }, "sha512-ll+9upzqt95ZBWcfkOszXZkfnpbJJ2CmxMfGgE5GmhdxxxCcO5bGhXkI+x8OpiS555RJ/v/sXJYMSOLkmu4fFw=="], - - "@reown/appkit/@walletconnect/universal-provider": ["@walletconnect/universal-provider@2.21.0", "", { "dependencies": { "@walletconnect/events": "1.0.1", "@walletconnect/jsonrpc-http-connection": "1.0.8", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "@walletconnect/sign-client": "2.21.0", "@walletconnect/types": "2.21.0", "@walletconnect/utils": "2.21.0", "es-toolkit": "1.33.0", "events": "3.3.0" } }, "sha512-mtUQvewt+X0VBQay/xOJBvxsB3Xsm1lTwFjZ6WUwSOTR1X+FNb71hSApnV5kbsdDIpYPXeQUbGt2se1n5E5UBg=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider": ["@walletconnect/universal-provider@2.21.0", "", { "dependencies": { "@walletconnect/events": "1.0.1", "@walletconnect/jsonrpc-http-connection": "1.0.8", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "@walletconnect/sign-client": "2.21.0", "@walletconnect/types": "2.21.0", "@walletconnect/utils": "2.21.0", "es-toolkit": "1.33.0", "events": "3.3.0" } }, "sha512-mtUQvewt+X0VBQay/xOJBvxsB3Xsm1lTwFjZ6WUwSOTR1X+FNb71hSApnV5kbsdDIpYPXeQUbGt2se1n5E5UBg=="], - - "@reown/appkit-polyfills/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - - "@reown/appkit-utils/@walletconnect/universal-provider": ["@walletconnect/universal-provider@2.21.0", "", { "dependencies": { "@walletconnect/events": "1.0.1", "@walletconnect/jsonrpc-http-connection": "1.0.8", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "@walletconnect/sign-client": "2.21.0", "@walletconnect/types": "2.21.0", "@walletconnect/utils": "2.21.0", "es-toolkit": "1.33.0", "events": "3.3.0" } }, "sha512-mtUQvewt+X0VBQay/xOJBvxsB3Xsm1lTwFjZ6WUwSOTR1X+FNb71hSApnV5kbsdDIpYPXeQUbGt2se1n5E5UBg=="], - - "@reown/appkit-wallet/zod": ["zod@3.22.4", "", {}, "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg=="], - "@sentry/bundler-plugin-core/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], "@sentry/bundler-plugin-core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], @@ -3106,8 +2648,6 @@ "@sentry/cli/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - "@solana/rpc-transport-http/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - "@tailwindcss/node/jiti": ["jiti@2.5.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="], @@ -3138,42 +2678,10 @@ "@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], - "@walletconnect/core/es-toolkit": ["es-toolkit@1.33.0", "", {}, "sha512-X13Q/ZSc+vsO1q600bvNK4bxgXMkHcf//RxCmYDaRY5DAcT+eoXjY5hoAPGMdRnWQjvyLEcyauG3b6hz76LNqg=="], - - "@walletconnect/environment/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], - - "@walletconnect/events/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], - - "@walletconnect/jsonrpc-http-connection/cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="], - - "@walletconnect/jsonrpc-utils/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], - - "@walletconnect/jsonrpc-ws-connection/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], - - "@walletconnect/relay-auth/@noble/curves": ["@noble/curves@1.8.0", "", { "dependencies": { "@noble/hashes": "1.7.0" } }, "sha512-j84kjAbzEnQHaSIhRPUmB3/eVXu2k3dKPl2LOrR8fSOIL+89U+7lV117EWHtq/GHM3ReGHM46iRBdZfpc4HRUQ=="], - - "@walletconnect/relay-auth/@noble/hashes": ["@noble/hashes@1.7.0", "", {}, "sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w=="], - - "@walletconnect/safe-json/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], - - "@walletconnect/time/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], - - "@walletconnect/universal-provider/es-toolkit": ["es-toolkit@1.33.0", "", {}, "sha512-X13Q/ZSc+vsO1q600bvNK4bxgXMkHcf//RxCmYDaRY5DAcT+eoXjY5hoAPGMdRnWQjvyLEcyauG3b6hz76LNqg=="], - - "@walletconnect/utils/@noble/ciphers": ["@noble/ciphers@1.2.1", "", {}, "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA=="], - - "@walletconnect/utils/@noble/curves": ["@noble/curves@1.8.1", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="], - - "@walletconnect/utils/@noble/hashes": ["@noble/hashes@1.7.1", "", {}, "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="], - - "@walletconnect/utils/viem": ["viem@2.23.2", "", { "dependencies": { "@noble/curves": "1.8.1", "@noble/hashes": "1.7.1", "@scure/bip32": "1.6.2", "@scure/bip39": "1.5.4", "abitype": "1.0.8", "isows": "1.0.6", "ox": "0.6.7", "ws": "8.18.0" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA=="], - - "@walletconnect/window-getters/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], - - "@walletconnect/window-metadata/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], - "accepts/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], + "agents/partysocket": ["partysocket@1.1.6", "", { "dependencies": { "event-target-polyfill": "^0.0.4" } }, "sha512-LkEk8N9hMDDsDT0iDK0zuwUDFVrVMUXFXCeN3850Ng8wtjPqPBeJlwdeY6ROlJSEh3tPoTTasXoSBYH76y118w=="], + "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], @@ -3188,10 +2696,6 @@ "browserify-sign/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], - "cbw-sdk/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - - "cbw-sdk/clsx": ["clsx@1.2.1", "", {}, "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="], - "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -3206,16 +2710,10 @@ "create-jest/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "cross-fetch/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - "diffie-hellman/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], "elliptic/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], - "engine.io-client/debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], - - "engine.io-client/ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="], - "eslint/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], @@ -3228,18 +2726,6 @@ "espree/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], - "eth-block-tracker/@metamask/utils": ["@metamask/utils@5.0.2", "", { "dependencies": { "@ethereumjs/tx": "^4.1.2", "@types/debug": "^4.1.7", "debug": "^4.3.4", "semver": "^7.3.8", "superstruct": "^1.0.3" } }, "sha512-yfmE79bRQtnMzarnKfX7AEJBwFTxvTyw3nBQlu/5rmGXrjAeAMltoGxO62TFurxrQAFMNa/fEjIHNvungZp0+g=="], - - "eth-json-rpc-filters/pify": ["pify@5.0.0", "", {}, "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA=="], - - "ethereum-cryptography/@noble/curves": ["@noble/curves@1.4.2", "", { "dependencies": { "@noble/hashes": "1.4.0" } }, "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw=="], - - "ethereum-cryptography/@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="], - - "ethereum-cryptography/@scure/bip32": ["@scure/bip32@1.4.0", "", { "dependencies": { "@noble/curves": "~1.4.0", "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg=="], - - "ethereum-cryptography/@scure/bip39": ["@scure/bip39@1.3.0", "", { "dependencies": { "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ=="], - "execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], @@ -3316,8 +2802,6 @@ "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], - "json-rpc-engine/@metamask/safe-event-emitter": ["@metamask/safe-event-emitter@2.0.0", "", {}, "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q=="], - "knip/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="], "loose-envify/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], @@ -3690,8 +3174,6 @@ "npm/yallist": ["yallist@2.1.2", "", {}, "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="], - "obj-multiplex/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], - "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "pbkdf2/create-hash": ["create-hash@1.1.3", "", { "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", "ripemd160": "^2.0.0", "sha.js": "^2.4.0" } }, "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA=="], @@ -3700,10 +3182,6 @@ "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], - "porto/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="], - - "porto/zustand": ["zustand@5.0.3", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg=="], - "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], @@ -3712,8 +3190,6 @@ "public-encrypt/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], - "qrcode/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], - "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "resolve-cwd/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], @@ -3728,10 +3204,6 @@ "simple-swizzle/is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="], - "socket.io-client/debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], - - "socket.io-parser/debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], - "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], "string-width/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], @@ -3744,18 +3216,8 @@ "type-is/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], - "unstorage/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - - "unstorage/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "valtio/use-sync-external-store": ["use-sync-external-store@1.2.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], - - "viem/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], - - "wagmi/use-sync-external-store": ["use-sync-external-store@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw=="], - "wrangler/@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.7.6", "", { "peerDependencies": { "unenv": "2.0.0-rc.21", "workerd": "^1.20250927.0" }, "optionalPeers": ["workerd"] }, "sha512-ykG2nd3trk6jbknRCH69xL3RpGLLbKCrbTbWSOvKEq7s4jH06yLrQlRr/q9IU+dK9p1JY1EXqhFK7VG5KqhzmQ=="], "wrangler/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], @@ -3774,8 +3236,6 @@ "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "@base-org/account/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - "@cloudflare/vite-plugin/wrangler/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], "@cloudflare/vitest-pool-workers/miniflare/undici": ["undici@7.16.0", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="], @@ -3790,8 +3250,6 @@ "@cloudflare/vitest-pool-workers/wrangler/workerd": ["workerd@1.20250906.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250906.0", "@cloudflare/workerd-darwin-arm64": "1.20250906.0", "@cloudflare/workerd-linux-64": "1.20250906.0", "@cloudflare/workerd-linux-arm64": "1.20250906.0", "@cloudflare/workerd-windows-64": "1.20250906.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-ryVyEaqXPPsr/AxccRmYZZmDAkfQVjhfRqrNTlEeN8aftBk6Ca1u7/VqmfOayjCXrA+O547TauebU+J3IpvFXw=="], - "@coinbase/wallet-sdk/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], @@ -3864,46 +3322,6 @@ "@jest/types/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "@metamask/eth-json-rpc-provider/@metamask/json-rpc-engine/@metamask/rpc-errors": ["@metamask/rpc-errors@6.4.0", "", { "dependencies": { "@metamask/utils": "^9.0.0", "fast-safe-stringify": "^2.0.6" } }, "sha512-1ugFO1UoirU2esS3juZanS/Fo8C8XYocCuBpfZI5N7ECtoG+zu0wF+uWZASik6CkO6w9n/Iebt4iI4pT0vptpg=="], - - "@metamask/eth-json-rpc-provider/@metamask/json-rpc-engine/@metamask/utils": ["@metamask/utils@8.5.0", "", { "dependencies": { "@ethereumjs/tx": "^4.2.0", "@metamask/superstruct": "^3.0.0", "@noble/hashes": "^1.3.1", "@scure/base": "^1.1.3", "@types/debug": "^4.1.7", "debug": "^4.3.4", "pony-cause": "^2.1.10", "semver": "^7.5.4", "uuid": "^9.0.1" } }, "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ=="], - - "@metamask/json-rpc-engine/@metamask/rpc-errors/@metamask/utils": ["@metamask/utils@9.3.0", "", { "dependencies": { "@ethereumjs/tx": "^4.2.0", "@metamask/superstruct": "^3.1.0", "@noble/hashes": "^1.3.1", "@scure/base": "^1.1.3", "@types/debug": "^4.1.7", "debug": "^4.3.4", "pony-cause": "^2.1.10", "semver": "^7.5.4", "uuid": "^9.0.1" } }, "sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g=="], - - "@metamask/json-rpc-engine/@metamask/utils/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - - "@metamask/json-rpc-middleware-stream/@metamask/utils/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - - "@metamask/providers/@metamask/rpc-errors/@metamask/utils": ["@metamask/utils@9.3.0", "", { "dependencies": { "@ethereumjs/tx": "^4.2.0", "@metamask/superstruct": "^3.1.0", "@noble/hashes": "^1.3.1", "@scure/base": "^1.1.3", "@types/debug": "^4.1.7", "debug": "^4.3.4", "pony-cause": "^2.1.10", "semver": "^7.5.4", "uuid": "^9.0.1" } }, "sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g=="], - - "@metamask/providers/@metamask/utils/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - - "@metamask/sdk-communication-layer/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], - - "@metamask/sdk/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/sign-client": ["@walletconnect/sign-client@2.21.0", "", { "dependencies": { "@walletconnect/core": "2.21.0", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.21.0", "@walletconnect/utils": "2.21.0", "events": "3.3.0" } }, "sha512-z7h+PeLa5Au2R591d/8ZlziE0stJvdzP9jNFzFolf2RG/OiXulgFKum8PrIyXy+Rg2q95U9nRVUF9fWcn78yBA=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/types": ["@walletconnect/types@2.21.0", "", { "dependencies": { "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "events": "3.3.0" } }, "sha512-ll+9upzqt95ZBWcfkOszXZkfnpbJJ2CmxMfGgE5GmhdxxxCcO5bGhXkI+x8OpiS555RJ/v/sXJYMSOLkmu4fFw=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils": ["@walletconnect/utils@2.21.0", "", { "dependencies": { "@noble/ciphers": "1.2.1", "@noble/curves": "1.8.1", "@noble/hashes": "1.7.1", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/relay-api": "1.0.11", "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.21.0", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "bs58": "6.0.0", "detect-browser": "5.3.0", "query-string": "7.1.3", "uint8arrays": "3.1.0", "viem": "2.23.2" } }, "sha512-zfHLiUoBrQ8rP57HTPXW7rQMnYxYI4gT9yTACxVW6LhIFROTF6/ytm5SKNoIvi4a5nX5dfXG4D9XwQUCu8Ilig=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/es-toolkit": ["es-toolkit@1.33.0", "", {}, "sha512-X13Q/ZSc+vsO1q600bvNK4bxgXMkHcf//RxCmYDaRY5DAcT+eoXjY5hoAPGMdRnWQjvyLEcyauG3b6hz76LNqg=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/sign-client": ["@walletconnect/sign-client@2.21.0", "", { "dependencies": { "@walletconnect/core": "2.21.0", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.21.0", "@walletconnect/utils": "2.21.0", "events": "3.3.0" } }, "sha512-z7h+PeLa5Au2R591d/8ZlziE0stJvdzP9jNFzFolf2RG/OiXulgFKum8PrIyXy+Rg2q95U9nRVUF9fWcn78yBA=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/types": ["@walletconnect/types@2.21.0", "", { "dependencies": { "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "events": "3.3.0" } }, "sha512-ll+9upzqt95ZBWcfkOszXZkfnpbJJ2CmxMfGgE5GmhdxxxCcO5bGhXkI+x8OpiS555RJ/v/sXJYMSOLkmu4fFw=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils": ["@walletconnect/utils@2.21.0", "", { "dependencies": { "@noble/ciphers": "1.2.1", "@noble/curves": "1.8.1", "@noble/hashes": "1.7.1", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/relay-api": "1.0.11", "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.21.0", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "bs58": "6.0.0", "detect-browser": "5.3.0", "query-string": "7.1.3", "uint8arrays": "3.1.0", "viem": "2.23.2" } }, "sha512-zfHLiUoBrQ8rP57HTPXW7rQMnYxYI4gT9yTACxVW6LhIFROTF6/ytm5SKNoIvi4a5nX5dfXG4D9XwQUCu8Ilig=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/es-toolkit": ["es-toolkit@1.33.0", "", {}, "sha512-X13Q/ZSc+vsO1q600bvNK4bxgXMkHcf//RxCmYDaRY5DAcT+eoXjY5hoAPGMdRnWQjvyLEcyauG3b6hz76LNqg=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/sign-client": ["@walletconnect/sign-client@2.21.0", "", { "dependencies": { "@walletconnect/core": "2.21.0", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.21.0", "@walletconnect/utils": "2.21.0", "events": "3.3.0" } }, "sha512-z7h+PeLa5Au2R591d/8ZlziE0stJvdzP9jNFzFolf2RG/OiXulgFKum8PrIyXy+Rg2q95U9nRVUF9fWcn78yBA=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils": ["@walletconnect/utils@2.21.0", "", { "dependencies": { "@noble/ciphers": "1.2.1", "@noble/curves": "1.8.1", "@noble/hashes": "1.7.1", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/relay-api": "1.0.11", "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.21.0", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "bs58": "6.0.0", "detect-browser": "5.3.0", "query-string": "7.1.3", "uint8arrays": "3.1.0", "viem": "2.23.2" } }, "sha512-zfHLiUoBrQ8rP57HTPXW7rQMnYxYI4gT9yTACxVW6LhIFROTF6/ytm5SKNoIvi4a5nX5dfXG4D9XwQUCu8Ilig=="], - - "@reown/appkit/@walletconnect/universal-provider/es-toolkit": ["es-toolkit@1.33.0", "", {}, "sha512-X13Q/ZSc+vsO1q600bvNK4bxgXMkHcf//RxCmYDaRY5DAcT+eoXjY5hoAPGMdRnWQjvyLEcyauG3b6hz76LNqg=="], - "@sentry/bundler-plugin-core/glob/minimatch": ["minimatch@8.0.4", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA=="], "@sentry/bundler-plugin-core/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="], @@ -3918,18 +3336,6 @@ "@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="], - "@walletconnect/jsonrpc-http-connection/cross-fetch/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - - "@walletconnect/utils/viem/@scure/bip32": ["@scure/bip32@1.6.2", "", { "dependencies": { "@noble/curves": "~1.8.1", "@noble/hashes": "~1.7.1", "@scure/base": "~1.2.2" } }, "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw=="], - - "@walletconnect/utils/viem/@scure/bip39": ["@scure/bip39@1.5.4", "", { "dependencies": { "@noble/hashes": "~1.7.1", "@scure/base": "~1.2.4" } }, "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA=="], - - "@walletconnect/utils/viem/abitype": ["abitype@1.0.8", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg=="], - - "@walletconnect/utils/viem/isows": ["isows@1.0.6", "", { "peerDependencies": { "ws": "*" } }, "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw=="], - - "@walletconnect/utils/viem/ox": ["ox@0.6.7", "", { "dependencies": { "@adraffy/ens-normalize": "^1.10.1", "@noble/curves": "^1.6.0", "@noble/hashes": "^1.5.0", "@scure/bip32": "^1.5.0", "@scure/bip39": "^1.4.0", "abitype": "^1.0.6", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA=="], - "accepts/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "babel-jest/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -3950,14 +3356,8 @@ "create-jest/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "engine.io-client/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], - "eslint/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "ethereum-cryptography/@scure/bip32/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], - - "ethereum-cryptography/@scure/bip39/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], - "express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "jest-circus/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], @@ -4082,40 +3482,18 @@ "npm/write-file-atomic/graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - "obj-multiplex/readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], - - "obj-multiplex/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], - - "obj-multiplex/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], - "pbkdf2/create-hash/ripemd160": ["ripemd160@2.0.2", "", { "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1" } }, "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA=="], "pbkdf2/ripemd160/hash-base": ["hash-base@2.0.2", "", { "dependencies": { "inherits": "^2.0.1" } }, "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw=="], "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], - "qrcode/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], - - "qrcode/yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], - - "qrcode/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "qrcode/yargs/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], - - "qrcode/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], - "send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "socket.io-client/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], - - "socket.io-parser/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], - "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "type-is/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "unstorage/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - "wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], "wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], @@ -4294,60 +3672,12 @@ "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], - "@metamask/eth-json-rpc-provider/@metamask/json-rpc-engine/@metamask/rpc-errors/@metamask/utils": ["@metamask/utils@9.3.0", "", { "dependencies": { "@ethereumjs/tx": "^4.2.0", "@metamask/superstruct": "^3.1.0", "@noble/hashes": "^1.3.1", "@scure/base": "^1.1.3", "@types/debug": "^4.1.7", "debug": "^4.3.4", "pony-cause": "^2.1.10", "semver": "^7.5.4", "uuid": "^9.0.1" } }, "sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g=="], - - "@metamask/eth-json-rpc-provider/@metamask/json-rpc-engine/@metamask/utils/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - - "@metamask/json-rpc-engine/@metamask/rpc-errors/@metamask/utils/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - - "@metamask/providers/@metamask/rpc-errors/@metamask/utils/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/sign-client/@walletconnect/core": ["@walletconnect/core@2.21.0", "", { "dependencies": { "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/jsonrpc-ws-connection": "1.0.16", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "@walletconnect/relay-api": "1.0.11", "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.21.0", "@walletconnect/utils": "2.21.0", "@walletconnect/window-getters": "1.0.1", "es-toolkit": "1.33.0", "events": "3.3.0", "uint8arrays": "3.1.0" } }, "sha512-o6R7Ua4myxR8aRUAJ1z3gT9nM+jd2B2mfamu6arzy1Cc6vi10fIwFWb6vg3bC8xJ6o9H3n/cN5TOW3aA9Y1XVw=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/@noble/ciphers": ["@noble/ciphers@1.2.1", "", {}, "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/@noble/curves": ["@noble/curves@1.8.1", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/@noble/hashes": ["@noble/hashes@1.7.1", "", {}, "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem": ["viem@2.23.2", "", { "dependencies": { "@noble/curves": "1.8.1", "@noble/hashes": "1.7.1", "@scure/bip32": "1.6.2", "@scure/bip39": "1.5.4", "abitype": "1.0.8", "isows": "1.0.6", "ox": "0.6.7", "ws": "8.18.0" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/sign-client/@walletconnect/core": ["@walletconnect/core@2.21.0", "", { "dependencies": { "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/jsonrpc-ws-connection": "1.0.16", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "@walletconnect/relay-api": "1.0.11", "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.21.0", "@walletconnect/utils": "2.21.0", "@walletconnect/window-getters": "1.0.1", "es-toolkit": "1.33.0", "events": "3.3.0", "uint8arrays": "3.1.0" } }, "sha512-o6R7Ua4myxR8aRUAJ1z3gT9nM+jd2B2mfamu6arzy1Cc6vi10fIwFWb6vg3bC8xJ6o9H3n/cN5TOW3aA9Y1XVw=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/@noble/ciphers": ["@noble/ciphers@1.2.1", "", {}, "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/@noble/curves": ["@noble/curves@1.8.1", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/@noble/hashes": ["@noble/hashes@1.7.1", "", {}, "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem": ["viem@2.23.2", "", { "dependencies": { "@noble/curves": "1.8.1", "@noble/hashes": "1.7.1", "@scure/bip32": "1.6.2", "@scure/bip39": "1.5.4", "abitype": "1.0.8", "isows": "1.0.6", "ox": "0.6.7", "ws": "8.18.0" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/sign-client/@walletconnect/core": ["@walletconnect/core@2.21.0", "", { "dependencies": { "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/jsonrpc-ws-connection": "1.0.16", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "@walletconnect/relay-api": "1.0.11", "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.21.0", "@walletconnect/utils": "2.21.0", "@walletconnect/window-getters": "1.0.1", "es-toolkit": "1.33.0", "events": "3.3.0", "uint8arrays": "3.1.0" } }, "sha512-o6R7Ua4myxR8aRUAJ1z3gT9nM+jd2B2mfamu6arzy1Cc6vi10fIwFWb6vg3bC8xJ6o9H3n/cN5TOW3aA9Y1XVw=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/@noble/ciphers": ["@noble/ciphers@1.2.1", "", {}, "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/@noble/curves": ["@noble/curves@1.8.1", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/@noble/hashes": ["@noble/hashes@1.7.1", "", {}, "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem": ["viem@2.23.2", "", { "dependencies": { "@noble/curves": "1.8.1", "@noble/hashes": "1.7.1", "@scure/bip32": "1.6.2", "@scure/bip39": "1.5.4", "abitype": "1.0.8", "isows": "1.0.6", "ox": "0.6.7", "ws": "8.18.0" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA=="], - "@sentry/bundler-plugin-core/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "@sentry/bundler-plugin-core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "@sentry/bundler-plugin-core/glob/path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - "@walletconnect/utils/viem/ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], - - "@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - - "@walletconnect/utils/viem/ox/@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], - - "@walletconnect/utils/viem/ox/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], - - "@walletconnect/utils/viem/ox/abitype": ["abitype@1.1.0", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A=="], - "npm/are-we-there-yet/readable-stream/process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], "npm/are-we-there-yet/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], @@ -4374,84 +3704,8 @@ "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], - "qrcode/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], - - "qrcode/yargs/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], - - "qrcode/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "qrcode/yargs/yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], - "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], - "@metamask/eth-json-rpc-provider/@metamask/json-rpc-engine/@metamask/rpc-errors/@metamask/utils/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/@scure/bip32": ["@scure/bip32@1.6.2", "", { "dependencies": { "@noble/curves": "~1.8.1", "@noble/hashes": "~1.7.1", "@scure/base": "~1.2.2" } }, "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/@scure/bip39": ["@scure/bip39@1.5.4", "", { "dependencies": { "@noble/hashes": "~1.7.1", "@scure/base": "~1.2.4" } }, "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/abitype": ["abitype@1.0.8", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/isows": ["isows@1.0.6", "", { "peerDependencies": { "ws": "*" } }, "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox": ["ox@0.6.7", "", { "dependencies": { "@adraffy/ens-normalize": "^1.10.1", "@noble/curves": "^1.6.0", "@noble/hashes": "^1.5.0", "@scure/bip32": "^1.5.0", "@scure/bip39": "^1.4.0", "abitype": "^1.0.6", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/@scure/bip32": ["@scure/bip32@1.6.2", "", { "dependencies": { "@noble/curves": "~1.8.1", "@noble/hashes": "~1.7.1", "@scure/base": "~1.2.2" } }, "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/@scure/bip39": ["@scure/bip39@1.5.4", "", { "dependencies": { "@noble/hashes": "~1.7.1", "@scure/base": "~1.2.4" } }, "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/abitype": ["abitype@1.0.8", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/isows": ["isows@1.0.6", "", { "peerDependencies": { "ws": "*" } }, "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox": ["ox@0.6.7", "", { "dependencies": { "@adraffy/ens-normalize": "^1.10.1", "@noble/curves": "^1.6.0", "@noble/hashes": "^1.5.0", "@scure/bip32": "^1.5.0", "@scure/bip39": "^1.4.0", "abitype": "^1.0.6", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/@scure/bip32": ["@scure/bip32@1.6.2", "", { "dependencies": { "@noble/curves": "~1.8.1", "@noble/hashes": "~1.7.1", "@scure/base": "~1.2.2" } }, "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/@scure/bip39": ["@scure/bip39@1.5.4", "", { "dependencies": { "@noble/hashes": "~1.7.1", "@scure/base": "~1.2.4" } }, "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/abitype": ["abitype@1.0.8", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/isows": ["isows@1.0.6", "", { "peerDependencies": { "ws": "*" } }, "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox": ["ox@0.6.7", "", { "dependencies": { "@adraffy/ens-normalize": "^1.10.1", "@noble/curves": "^1.6.0", "@noble/hashes": "^1.5.0", "@scure/bip32": "^1.5.0", "@scure/bip39": "^1.4.0", "abitype": "^1.0.6", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA=="], - "pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], - - "qrcode/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "qrcode/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], - - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/abitype": ["abitype@1.1.0", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], - - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/abitype": ["abitype@1.1.0", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], - - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/abitype": ["abitype@1.1.0", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A=="], - - "qrcode/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], } } diff --git a/package.json b/package.json index 95d8468c..21c52674 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "@typescript-eslint/typescript-estree": "^8.44.1", "@vitejs/plugin-react": "^5.0.4", "@vitejs/plugin-react-oxc": "^0.4.2", - "agents": "^0.1.6", + "agents": "^0.2.14", "chalk": "^5.6.2", "class-variance-authority": "^0.7.1", "cloudflare": "^4.5.0", From e01f067969e1ad414257a312d7c887216c06fe46 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Mon, 20 Oct 2025 16:38:21 -0400 Subject: [PATCH 046/150] feat: in memory get template details --- worker/agents/core/simpleGeneratorAgent.ts | 3 +- worker/agents/core/types.ts | 1 - worker/agents/index.ts | 61 ++++++------ worker/api/controllers/agent/controller.ts | 3 +- worker/services/sandbox/BaseSandboxService.ts | 87 ++++++++++++++++- .../services/sandbox/remoteSandboxService.ts | 8 -- worker/services/sandbox/sandboxSdkClient.ts | 93 ------------------- 7 files changed, 114 insertions(+), 142 deletions(-) diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index 700bd2c2..edd37725 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -185,7 +185,8 @@ export class SimpleCodeGeneratorAgent extends Agent { ..._args: unknown[] ): Promise { - const { query, language, frameworks, hostname, inferenceContext, templateInfo, sandboxSessionId } = initArgs; + const { query, language, frameworks, hostname, inferenceContext, templateInfo } = initArgs; + const sandboxSessionId = inferenceContext.agentId; // Let the initial sessionId be the agentId this.initLogger(inferenceContext.agentId, sandboxSessionId, inferenceContext.userId); // Generate a blueprint diff --git a/worker/agents/core/types.ts b/worker/agents/core/types.ts index 55aee1d6..af7ecc27 100644 --- a/worker/agents/core/types.ts +++ b/worker/agents/core/types.ts @@ -18,7 +18,6 @@ export interface AgentInitArgs { templateDetails: TemplateDetails; selection: TemplateSelection; } - sandboxSessionId: string images?: ProcessedImageAttachment[]; onBlueprintChunk: (chunk: string) => void; } diff --git a/worker/agents/index.ts b/worker/agents/index.ts index e5b445d3..db66b2df 100644 --- a/worker/agents/index.ts +++ b/worker/agents/index.ts @@ -7,10 +7,10 @@ import { StructuredLogger } from '../logger'; import { InferenceContext } from './inferutils/config.types'; import { SandboxSdkClient } from '../services/sandbox/sandboxSdkClient'; import { selectTemplate } from './planning/templateSelector'; -import { getSandboxService } from '../services/sandbox/factory'; import { TemplateDetails } from '../services/sandbox/sandboxTypes'; import { TemplateSelection } from './schemas'; import type { ImageAttachment } from '../types/image-attachment'; +import { BaseSandboxService } from 'worker/services/sandbox/BaseSandboxService'; export async function getAgentStub(env: Env, agentId: string, searchInOtherJurisdictions: boolean = false, logger: StructuredLogger) : Promise> { if (searchInOtherJurisdictions) { @@ -77,46 +77,39 @@ export async function getTemplateForQuery( query: string, images: ImageAttachment[] | undefined, logger: StructuredLogger, -) : Promise<{sandboxSessionId: string, templateDetails: TemplateDetails, selection: TemplateSelection}> { +) : Promise<{templateDetails: TemplateDetails, selection: TemplateSelection}> { // Fetch available templates const templatesResponse = await SandboxSdkClient.listTemplates(); if (!templatesResponse || !templatesResponse.success) { throw new Error(`Failed to fetch templates from sandbox service, ${templatesResponse.error}`); } - - const sandboxSessionId = generateId(); - - const [analyzeQueryResponse, sandboxClient] = await Promise.all([ - selectTemplate({ - env: env, - inferenceContext, - query, - availableTemplates: templatesResponse.templates, - images, - }), - getSandboxService(sandboxSessionId, 'default') - ]); - logger.info('Selected template', { selectedTemplate: analyzeQueryResponse }); + const analyzeQueryResponse = await selectTemplate({ + env, + inferenceContext, + query, + availableTemplates: templatesResponse.templates, + images, + }); + + logger.info('Selected template', { selectedTemplate: analyzeQueryResponse }); - // Find the selected template by name in the available templates - if (!analyzeQueryResponse.selectedTemplateName) { - logger.error('No suitable template found for code generation'); - throw new Error('No suitable template found for code generation'); - } + if (!analyzeQueryResponse.selectedTemplateName) { + logger.error('No suitable template found for code generation'); + throw new Error('No suitable template found for code generation'); + } - const selectedTemplate = templatesResponse.templates.find(template => template.name === analyzeQueryResponse.selectedTemplateName); - if (!selectedTemplate) { - logger.error('Selected template not found'); - throw new Error('Selected template not found'); - } - // Now fetch all the files from the instance - const templateDetailsResponse = await sandboxClient.getTemplateDetails(selectedTemplate.name); - if (!templateDetailsResponse.success || !templateDetailsResponse.templateDetails) { - logger.error('Failed to fetch files', { templateDetailsResponse }); - throw new Error('Failed to fetch files'); - } + const selectedTemplate = templatesResponse.templates.find(template => template.name === analyzeQueryResponse.selectedTemplateName); + if (!selectedTemplate) { + logger.error('Selected template not found'); + throw new Error('Selected template not found'); + } + const templateDetailsResponse = await BaseSandboxService.getTemplateDetails(selectedTemplate.name); + if (!templateDetailsResponse.success || !templateDetailsResponse.templateDetails) { + logger.error('Failed to fetch files', { templateDetailsResponse }); + throw new Error('Failed to fetch files'); + } - const templateDetails = templateDetailsResponse.templateDetails; - return { sandboxSessionId, templateDetails, selection: analyzeQueryResponse }; + const templateDetails = templateDetailsResponse.templateDetails; + return { templateDetails, selection: analyzeQueryResponse }; } \ No newline at end of file diff --git a/worker/api/controllers/agent/controller.ts b/worker/api/controllers/agent/controller.ts index 2f4326cb..71275cdd 100644 --- a/worker/api/controllers/agent/controller.ts +++ b/worker/api/controllers/agent/controller.ts @@ -111,7 +111,7 @@ export class CodingAgentController extends BaseController { modelConfigsCount: Object.keys(userModelConfigs).length, }); - const { sandboxSessionId, templateDetails, selection } = await getTemplateForQuery(env, inferenceContext, query, body.images, this.logger); + const { templateDetails, selection } = await getTemplateForQuery(env, inferenceContext, query, body.images, this.logger); const websocketUrl = `${url.protocol === 'https:' ? 'wss:' : 'ws:'}//${url.host}/api/agent/${agentId}/ws`; const httpStatusUrl = `${url.origin}/api/agent/${agentId}`; @@ -145,7 +145,6 @@ export class CodingAgentController extends BaseController { writer.write({chunk}); }, templateInfo: { templateDetails, selection }, - sandboxSessionId }, body.agentMode || defaultCodeGenArgs.agentMode) as Promise; agentPromise.then(async (_state: CodeGenState) => { writer.write("terminate"); diff --git a/worker/services/sandbox/BaseSandboxService.ts b/worker/services/sandbox/BaseSandboxService.ts index 9410a53a..63b8eeec 100644 --- a/worker/services/sandbox/BaseSandboxService.ts +++ b/worker/services/sandbox/BaseSandboxService.ts @@ -29,11 +29,14 @@ import { ListInstancesResponse, GitHubPushRequest, GitHubPushResponse, + TemplateDetails, } from './sandboxTypes'; import { createObjectLogger, StructuredLogger } from '../../logger'; import { env } from 'cloudflare:workers' import { FileOutputType } from 'worker/agents/schemas'; +import { ZipExtractor } from './zipExtractor'; +import { FileTreeBuilder } from './fileTreeBuilder'; /** * Streaming event for enhanced command execution @@ -113,11 +116,89 @@ import { FileOutputType } from 'worker/agents/schemas'; } /** - * Get details for a specific template including files and structure + * Get details for a specific template - fully in-memory, no sandbox operations + * Downloads zip from R2, extracts in memory, and returns all files with metadata * Returns: { success: boolean, templateDetails?: {...}, error?: string } */ - abstract getTemplateDetails(templateName: string): Promise; - + static async getTemplateDetails(templateName: string, downloadDir?: string): Promise { + try { + // Download template zip from R2 + const downloadUrl = downloadDir ? `${downloadDir}/${templateName}.zip` : `${templateName}.zip`; + const r2Object = await env.TEMPLATES_BUCKET.get(downloadUrl); + + if (!r2Object) { + throw new Error(`Template '${templateName}' not found in bucket`); + } + + const zipData = await r2Object.arrayBuffer(); + + // Extract all files in memory + const allFiles = ZipExtractor.extractFiles(zipData); + + // Build file tree + const fileTree = FileTreeBuilder.buildFromTemplateFiles(allFiles, { rootPath: '.' }); + + // Extract dependencies from package.json + const packageJsonFile = allFiles.find(f => f.filePath === 'package.json'); + const packageJson = packageJsonFile ? JSON.parse(packageJsonFile.fileContents) : null; + const dependencies = packageJson?.dependencies || {}; + + // Parse metadata files + const dontTouchFile = allFiles.find(f => f.filePath === '.donttouch_files.json'); + const dontTouchFiles = dontTouchFile ? JSON.parse(dontTouchFile.fileContents) : []; + + const redactedFile = allFiles.find(f => f.filePath === '.redacted_files.json'); + const redactedFiles = redactedFile ? JSON.parse(redactedFile.fileContents) : []; + + const importantFile = allFiles.find(f => f.filePath === '.important_files.json'); + const importantFiles = importantFile ? JSON.parse(importantFile.fileContents) : []; + + // Get template info from catalog + const catalogResponse = await BaseSandboxService.listTemplates(); + const catalogInfo = catalogResponse.success + ? catalogResponse.templates.find(t => t.name === templateName) + : null; + + // Remove metadata files and convert to map for efficient lookups + const filteredFiles = allFiles.filter(f => + !f.filePath.startsWith('.') || + (!f.filePath.endsWith('.json') && !f.filePath.startsWith('.git')) + ); + + // Convert array to map: filePath -> fileContents + const filesMap: Record = {}; + for (const file of filteredFiles) { + filesMap[file.filePath] = file.fileContents; + } + + const templateDetails: TemplateDetails = { + name: templateName, + description: { + selection: catalogInfo?.description.selection || '', + usage: catalogInfo?.description.usage || '' + }, + fileTree, + allFiles: filesMap, + language: catalogInfo?.language, + deps: dependencies, + importantFiles, + dontTouchFiles, + redactedFiles, + frameworks: catalogInfo?.frameworks || [] + }; + + return { + success: true, + templateDetails + }; + } catch (error) { + return { + success: false, + error: `Failed to get template details: ${error instanceof Error ? error.message : 'Unknown error'}` + }; + } + } + // ========================================== // INSTANCE LIFECYCLE (Required) // ========================================== diff --git a/worker/services/sandbox/remoteSandboxService.ts b/worker/services/sandbox/remoteSandboxService.ts index 69acc93f..42f06085 100644 --- a/worker/services/sandbox/remoteSandboxService.ts +++ b/worker/services/sandbox/remoteSandboxService.ts @@ -1,5 +1,4 @@ import { - TemplateDetailsResponse, BootstrapResponse, GetInstanceResponse, BootstrapStatusResponse, @@ -14,7 +13,6 @@ import { DeploymentResult, GetLogsResponse, ListInstancesResponse, - TemplateDetailsResponseSchema, BootstrapResponseSchema, BootstrapRequest, GetInstanceResponseSchema, @@ -115,12 +113,6 @@ export class RemoteSandboxServiceClient extends BaseSandboxService{ }; } } - /** - * Get details for a specific template. - */ - async getTemplateDetails(templateName: string): Promise { - return this.makeRequest(`/templates/${templateName}`, 'GET', TemplateDetailsResponseSchema); - } /** * Create a new runner instance. diff --git a/worker/services/sandbox/sandboxSdkClient.ts b/worker/services/sandbox/sandboxSdkClient.ts index 2f21e956..292196ce 100644 --- a/worker/services/sandbox/sandboxSdkClient.ts +++ b/worker/services/sandbox/sandboxSdkClient.ts @@ -1,7 +1,6 @@ import { getSandbox, Sandbox, parseSSEStream, LogEvent, ExecResult } from '@cloudflare/sandbox'; import { - TemplateDetailsResponse, BootstrapResponse, GetInstanceResponse, BootstrapStatusResponse, @@ -24,8 +23,6 @@ import { GetLogsResponse, ListInstancesResponse, StoredError, - TemplateInfo, - TemplateDetails, } from './sandboxTypes'; import { createObjectLogger } from '../../logger'; @@ -393,96 +390,6 @@ export class SandboxSdkClient extends BaseSandboxService { this.logger.info(`Template already exists`); } } - - - async getTemplateDetails(templateName: string): Promise { - try { - this.logger.info('Retrieving template details', { templateName }); - - await this.ensureTemplateExists(templateName); - - this.logger.info('Template setup complete'); - - const [fileTree, catalogInfo, dontTouchFiles, redactedFiles] = await Promise.all([ - this.buildFileTree(templateName), - this.getTemplateFromCatalog(templateName), - this.fetchDontTouchFiles(templateName), - this.fetchRedactedFiles(templateName) - ]); - - if (!fileTree) { - throw new Error(`Failed to build file tree for template ${templateName}`); - } - - const filesResponse = await this.getFiles(templateName, undefined, true, redactedFiles); // Use template name as directory - - this.logger.info('Template files retrieved'); - - // Parse package.json for dependencies - let dependencies: Record = {}; - try { - const packageJsonFile = filesResponse.files.find(file => file.filePath === 'package.json'); - if (!packageJsonFile) { - throw new Error('package.json not found'); - } - const packageJson = JSON.parse(packageJsonFile.fileContents) as { - dependencies?: Record; - devDependencies?: Record; - }; - dependencies = { - ...packageJson.dependencies || {}, - ...packageJson.devDependencies || {} - }; - } catch { - this.logger.info('No package.json found', { templateName }); - } - - const allFiles = filesResponse.files.reduce((acc, file) => { - acc[file.filePath] = file.fileContents; - return acc; - }, {} as Record); - const templateDetails: TemplateDetails = { - name: templateName, - description: { - selection: catalogInfo?.description.selection || '', - usage: catalogInfo?.description.usage || '' - }, - fileTree, - allFiles, - importantFiles: filesResponse.files.map(file => file.filePath), - language: catalogInfo?.language, - deps: dependencies, - dontTouchFiles, - redactedFiles, - frameworks: catalogInfo?.frameworks || [] - }; - - this.logger.info('Template files retrieved', { templateName, fileCount: filesResponse.files.length }); - - return { - success: true, - templateDetails - }; - } catch (error) { - this.logger.error('getTemplateDetails', error, { templateName }); - return { - success: false, - error: `Failed to get template details: ${error instanceof Error ? error.message : 'Unknown error'}` - }; - } - } - - private async getTemplateFromCatalog(templateName: string): Promise { - try { - const templatesResponse = await SandboxSdkClient.listTemplates(); - if (templatesResponse.success) { - return templatesResponse.templates.find(t => t.name === templateName) || null; - } - return null; - } catch { - return null; - } - } private async buildFileTree(instanceId: string): Promise { try { From b73557e4ed141191aba31a1fd6dceb7cbb86ec14 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Mon, 20 Oct 2025 16:55:30 -0400 Subject: [PATCH 047/150] fix: finish posting to files map --- worker/agents/domain/pure/FileProcessing.ts | 8 +++----- worker/agents/planning/blueprint.ts | 3 ++- worker/services/sandbox/utils.ts | 7 +++++++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/worker/agents/domain/pure/FileProcessing.ts b/worker/agents/domain/pure/FileProcessing.ts index d8fbbc78..b3b809d7 100644 --- a/worker/agents/domain/pure/FileProcessing.ts +++ b/worker/agents/domain/pure/FileProcessing.ts @@ -3,6 +3,7 @@ import type { StructuredLogger } from '../../../logger'; import { TemplateDetails } from '../../../services/sandbox/sandboxTypes'; import { applyUnifiedDiff } from '../../output-formats/diff-formats'; import { FileState } from 'worker/agents/core/state'; +import { getTemplateFiles } from 'worker/services/sandbox/utils'; /** * File processing utilities @@ -94,11 +95,7 @@ export class FileProcessing { templateDetails: TemplateDetails | undefined, generatedFilesMap: Record ): FileState[] { - const templateFiles = templateDetails?.files.map(file => ({ - filePath: file.filePath, - fileContents: file.fileContents, - filePurpose: 'Boilerplate template file' - })) || []; + const templateFiles = templateDetails?.allFiles ? getTemplateFiles(templateDetails) : []; // Filter out template files that have been overridden by generated files const nonOverriddenTemplateFiles = templateFiles.filter( @@ -108,6 +105,7 @@ export class FileProcessing { return [ ...nonOverriddenTemplateFiles.map(file => ({ ...file, + filePurpose: 'Boilerplate template file', lastDiff: '', lastmodified: Date.now(), unmerged: [], diff --git a/worker/agents/planning/blueprint.ts b/worker/agents/planning/blueprint.ts index 3f0adb42..726320f7 100644 --- a/worker/agents/planning/blueprint.ts +++ b/worker/agents/planning/blueprint.ts @@ -9,6 +9,7 @@ import { TemplateRegistry } from '../inferutils/schemaFormatters'; import z from 'zod'; import { imagesToBase64 } from 'worker/utils/images'; import { ProcessedImageAttachment } from 'worker/types/image-attachment'; +import { getTemplateFiles } from 'worker/services/sandbox/utils'; const logger = createLogger('Blueprint'); @@ -187,7 +188,7 @@ export async function generateBlueprint({ env, inferenceContext, query, language // --------------------------------------------------------------------------- const filesText = TemplateRegistry.markdown.serialize( - { files: templateDetails.files.filter(f => !f.filePath.includes('package.json')) }, + { files: getTemplateFiles(templateDetails).filter(f => !f.filePath.includes('package.json')) }, z.object({ files: z.array(TemplateFileSchema) }) ); diff --git a/worker/services/sandbox/utils.ts b/worker/services/sandbox/utils.ts index 2796564b..18bd6519 100644 --- a/worker/services/sandbox/utils.ts +++ b/worker/services/sandbox/utils.ts @@ -5,4 +5,11 @@ export function getTemplateImportantFiles(templateDetails: TemplateDetails): Tem filePath, fileContents: templateDetails.allFiles[filePath], })); +} + +export function getTemplateFiles(templateDetails: TemplateDetails): TemplateFile[] { + return Object.entries(templateDetails.allFiles).map(([filePath, fileContents]) => ({ + filePath, + fileContents, + })); } \ No newline at end of file From af1ba3155e458320abc6d67c60ca7e4276885ec4 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Mon, 20 Oct 2025 17:09:05 -0400 Subject: [PATCH 048/150] fix: rendering and migration for template details change --- .../chat/utils/handle-websocket-message.ts | 9 +++++++-- worker/agents/core/stateMigration.ts | 16 +++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/routes/chat/utils/handle-websocket-message.ts b/src/routes/chat/utils/handle-websocket-message.ts index dd93d9db..b16164f1 100644 --- a/src/routes/chat/utils/handle-websocket-message.ts +++ b/src/routes/chat/utils/handle-websocket-message.ts @@ -168,8 +168,13 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { setQuery(state.query); } - if (state.templateDetails?.files && bootstrapFiles.length === 0) { - loadBootstrapFiles(state.templateDetails.files); + if (state.templateDetails?.allFiles && bootstrapFiles.length === 0) { + const files = Object.entries(state.templateDetails.allFiles).map(([filePath, fileContents]) => ({ + filePath, + fileContents, + })); + logger.debug('📥 Restoring bootstrap files:', files); + loadBootstrapFiles(files); } if (state.generatedFilesMap && files.length === 0) { diff --git a/worker/agents/core/stateMigration.ts b/worker/agents/core/stateMigration.ts index 30c4338a..fd62b7a9 100644 --- a/worker/agents/core/stateMigration.ts +++ b/worker/agents/core/stateMigration.ts @@ -45,13 +45,15 @@ export class StateMigration { acc[file.filePath] = file; return acc; }, {} as Record); - - if (needsMigration) { - migratedTemplateDetails = { - ...migratedTemplateDetails, - allFiles - }; - } + + migratedTemplateDetails = { + ...migratedTemplateDetails, + allFiles + }; + + // Remove 'files' property + delete (migratedTemplateDetails as any).files; + needsMigration = true; } let migratedConversationMessages = state.conversationMessages; From 3cd14ed58a4fe786523a4e5c63348c0b2671ee78 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Mon, 20 Oct 2025 22:02:35 -0400 Subject: [PATCH 049/150] feat: purely in-isolate template unzip + inmemory storage --- .../chat/utils/handle-websocket-message.ts | 19 +++-- worker/agents/core/simpleGeneratorAgent.ts | 83 ++++++++++++++----- worker/agents/core/state.ts | 3 +- worker/agents/core/stateMigration.ts | 48 ++++++----- .../implementations/DeploymentManager.ts | 4 +- .../services/implementations/FileManager.ts | 8 +- worker/api/websocketTypes.ts | 9 +- worker/services/sandbox/BaseSandboxService.ts | 51 +++++++----- 8 files changed, 146 insertions(+), 79 deletions(-) diff --git a/src/routes/chat/utils/handle-websocket-message.ts b/src/routes/chat/utils/handle-websocket-message.ts index b16164f1..413bf0f9 100644 --- a/src/routes/chat/utils/handle-websocket-message.ts +++ b/src/routes/chat/utils/handle-websocket-message.ts @@ -152,10 +152,10 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { })); break; } - case 'cf_agent_state': { - const { state } = message; - logger.debug('🔄 Agent state update received:', state); - + case 'agent_connected': { + const { state, templateDetails } = message; + console.log('Agent connected', state, templateDetails); + if (!isInitialStateRestored) { logger.debug('📥 Performing initial state restoration'); @@ -168,11 +168,11 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { setQuery(state.query); } - if (state.templateDetails?.allFiles && bootstrapFiles.length === 0) { - const files = Object.entries(state.templateDetails.allFiles).map(([filePath, fileContents]) => ({ + if (templateDetails?.allFiles && bootstrapFiles.length === 0) { + const files = Object.entries(templateDetails.allFiles).map(([filePath, fileContents]) => ({ filePath, fileContents, - })); + })).filter((file) => templateDetails.importantFiles.includes(file.filePath)); logger.debug('📥 Restoring bootstrap files:', files); loadBootstrapFiles(files); } @@ -254,6 +254,11 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { sendWebSocketMessage(websocket, 'preview'); } } + break; + } + case 'cf_agent_state': { + const { state } = message; + logger.debug('🔄 Agent state update received:', state); if (state.shouldBeGenerating) { logger.debug('🔄 shouldBeGenerating=true detected, auto-resuming generation'); diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index edd37725..e4bd0af8 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -1,4 +1,4 @@ -import { Agent, AgentContext, Connection } from 'agents'; +import { Agent, AgentContext, Connection, ConnectionContext } from 'agents'; import { Blueprint, PhaseConceptGenerationSchemaType, @@ -12,7 +12,7 @@ import { GitHubExportResult } from '../../services/github/types'; import { CodeGenState, CurrentDevState, MAX_PHASES } from './state'; import { AllIssues, AgentSummary, AgentInitArgs, PhaseExecutionResult, UserContext } from './types'; import { PREVIEW_EXPIRED_ERROR, WebSocketMessageResponses } from '../constants'; -import { broadcastToConnections, handleWebSocketClose, handleWebSocketMessage } from './websocket'; +import { broadcastToConnections, handleWebSocketClose, handleWebSocketMessage, sendToConnection } from './websocket'; import { createObjectLogger, StructuredLogger } from '../../logger'; import { ProjectSetupAssistant } from '../assistants/projectsetup'; import { UserConversationProcessor, RenderToolCall } from '../operations/UserConversationProcessor'; @@ -82,6 +82,7 @@ export class SimpleCodeGeneratorAgent extends Agent { protected deploymentManager!: DeploymentManager; private previewUrlCache: string = ''; + private templateDetailsCache: TemplateDetails | null = null; // In-memory storage for user-uploaded images (not persisted in DO state) private pendingUserImages: ProcessedImageAttachment[] = [] @@ -131,7 +132,7 @@ export class SimpleCodeGeneratorAgent extends Agent { generatedFilesMap: {}, agentMode: 'deterministic', sandboxInstanceId: undefined, - templateDetails: {} as TemplateDetails, + templateName: '', commandsHistory: [], lastPackageJson: '', pendingUserInputs: [], @@ -160,7 +161,7 @@ export class SimpleCodeGeneratorAgent extends Agent { ); // Initialize FileManager - this.fileManager = new FileManager(this.stateManager); + this.fileManager = new FileManager(this.stateManager, () => this.getTemplateDetails()); // Initialize DeploymentManager first (manages sandbox client caching) // DeploymentManager will use its own getClient() override for caching @@ -212,12 +213,14 @@ export class SimpleCodeGeneratorAgent extends Agent { }) const packageJson = templateInfo.templateDetails?.allFiles['package.json']; + + this.templateDetailsCache = templateInfo.templateDetails; this.setState({ ...this.initialState, query, blueprint, - templateDetails: templateInfo.templateDetails, + templateName: templateInfo.templateDetails.name, sandboxInstanceId: undefined, generatedPhases: [], commandsHistory: [], @@ -253,7 +256,59 @@ export class SimpleCodeGeneratorAgent extends Agent { async isInitialized() { return this.getAgentId() ? true : false - } + } + + async onStart(_props?: Record | undefined): Promise { + this.logger().info(`Agent ${this.getAgentId()} session: ${this.state.sessionId} onStart`); + // Ignore if agent not initialized + if (!this.isInitialized()) { + this.logger().info(`Agent ${this.getAgentId()} session: ${this.state.sessionId} not initialized, ignoring onStart`); + return; + } + this.logger().info(`Agent ${this.getAgentId()} session: ${this.state.sessionId} onStart being processed`); + // Fill the template cache + await this.ensureTemplateDetails(); + this.logger().info(`Agent ${this.getAgentId()} session: ${this.state.sessionId} onStart processed successfully`); + } + + onStateUpdate(_state: CodeGenState, _source: "server" | Connection) {} + + setState(state: CodeGenState): void { + try { + super.setState(state); + } catch (error) { + this.broadcastError("Error setting state", error); + this.logger().error("State details:", { + originalState: JSON.stringify(this.state, null, 2), + newState: JSON.stringify(state, null, 2) + }); + } + } + + onConnect(connection: Connection, ctx: ConnectionContext) { + this.logger().info(`Agent connected for agent ${this.getAgentId()}`, { connection, ctx }); + sendToConnection(connection, 'agent_connected', { + state: this.state, + templateDetails: this.getTemplateDetails() + }); + } + + async ensureTemplateDetails() { + if (!this.templateDetailsCache) { + this.logger().info(`Template details being cached for template: ${this.state.templateName}`); + const results = await BaseSandboxService.getTemplateDetails(this.state.templateName); + if (!results.success || !results.templateDetails) { + throw new Error(`Failed to get template details for template: ${this.state.templateName}`); + } + this.templateDetailsCache = results.templateDetails; + this.logger().info(`Template details for template: ${this.state.templateName} cached successfully`); + } + return this.templateDetailsCache; + } + + private getTemplateDetails() { + return this.templateDetailsCache!; + } /* * Each DO has 10 gb of sqlite storage. However, the way agents sdk works, it stores the 'state' object of the agent as a single row @@ -333,20 +388,6 @@ export class SimpleCodeGeneratorAgent extends Agent { }); this.logger().info(`Agent initialized successfully for agent ${this.state.inferenceContext.agentId}`); } - - onStateUpdate(_state: CodeGenState, _source: "server" | Connection) {} - - setState(state: CodeGenState): void { - try { - super.setState(state); - } catch (error) { - this.broadcastError("Error setting state", error); - this.logger().error("State details:", { - originalState: JSON.stringify(this.state, null, 2), - newState: JSON.stringify(state, null, 2) - }); - } - } getPreviewUrlCache() { return this.previewUrlCache; @@ -359,7 +400,7 @@ export class SimpleCodeGeneratorAgent extends Agent { agentId: this.getAgentId(), query: this.state.query, blueprint: this.state.blueprint, - template: this.state.templateDetails, + template: this.getTemplateDetails(), inferenceContext: this.state.inferenceContext }); } diff --git a/worker/agents/core/state.ts b/worker/agents/core/state.ts index 18716357..6ef7e1db 100644 --- a/worker/agents/core/state.ts +++ b/worker/agents/core/state.ts @@ -1,7 +1,6 @@ import type { Blueprint, PhaseConceptType , FileOutputType, } from '../schemas'; -import type { TemplateDetails } from '../../services/sandbox/sandboxTypes'; // import type { ScreenshotData } from './types'; import type { ConversationMessage } from '../inferutils/common'; import type { InferenceContext } from '../inferutils/config.types'; @@ -36,7 +35,7 @@ export interface CodeGenState { generatedPhases: PhaseState[]; commandsHistory?: string[]; // History of commands run lastPackageJson?: string; // Last package.json file contents - templateDetails: TemplateDetails; // TODO: Remove this from state and rely on directly fetching from sandbox + templateName: string; sandboxInstanceId?: string; shouldBeGenerating: boolean; // Persistent flag indicating generation should be active diff --git a/worker/agents/core/stateMigration.ts b/worker/agents/core/stateMigration.ts index fd62b7a9..b71a313f 100644 --- a/worker/agents/core/stateMigration.ts +++ b/worker/agents/core/stateMigration.ts @@ -1,10 +1,14 @@ import { CodeGenState, FileState } from './state'; import { StructuredLogger } from '../../logger'; +import { TemplateDetails } from 'worker/services/sandbox/sandboxTypes'; export class StateMigration { static migrateIfNeeded(state: CodeGenState, logger: StructuredLogger): CodeGenState | null { let needsMigration = false; + //------------------------------------------------------------------------------------ + // Migrate files from old schema + //------------------------------------------------------------------------------------ const migrateFile = (file: any): any => { const hasOldFormat = 'file_path' in file || 'file_contents' in file || 'file_purpose' in file; @@ -34,27 +38,9 @@ export class StateMigration { } } - let migratedTemplateDetails = state.templateDetails; - if ('files' in migratedTemplateDetails && migratedTemplateDetails?.files) { - const migratedTemplateFiles = (migratedTemplateDetails.files as Array).map(file => { - const migratedFile = migrateFile(file); - return migratedFile; - }); - - const allFiles = migratedTemplateFiles.reduce((acc, file) => { - acc[file.filePath] = file; - return acc; - }, {} as Record); - - migratedTemplateDetails = { - ...migratedTemplateDetails, - allFiles - }; - - // Remove 'files' property - delete (migratedTemplateDetails as any).files; - needsMigration = true; - } + //------------------------------------------------------------------------------------ + // Migrate conversations cleanups and internal memos + //------------------------------------------------------------------------------------ let migratedConversationMessages = state.conversationMessages; const MIN_MESSAGES_FOR_CLEANUP = 25; @@ -132,6 +118,9 @@ export class StateMigration { } } + //------------------------------------------------------------------------------------ + // Migrate inference context from old schema + //------------------------------------------------------------------------------------ let migratedInferenceContext = state.inferenceContext; if (migratedInferenceContext && 'userApiKeys' in migratedInferenceContext) { migratedInferenceContext = { @@ -142,6 +131,9 @@ export class StateMigration { needsMigration = true; } + //------------------------------------------------------------------------------------ + // Migrate deprecated props + //------------------------------------------------------------------------------------ const stateHasDeprecatedProps = 'latestScreenshot' in (state as any); if (stateHasDeprecatedProps) { needsMigration = true; @@ -151,11 +143,22 @@ export class StateMigration { if (!stateHasProjectUpdatesAccumulator) { needsMigration = true; } + + //------------------------------------------------------------------------------------ + // Migrate Template Details -> remove template details and instead use template name + //------------------------------------------------------------------------------------ + const hasTemplateDetails = 'templateDetails' in (state as any); + if (hasTemplateDetails) { + needsMigration = true; + const templateDetails = (state as any).templateDetails; + const templateName = (templateDetails as TemplateDetails).name; + delete (state as any).templateDetails; + (state as any).templateName = templateName; + } if (needsMigration) { logger.info('Migrating state: schema format, conversation cleanup, and security fixes', { generatedFilesCount: Object.keys(migratedFilesMap).length, - templateFilesCount: migratedTemplateDetails?.allFiles?.length || 0, finalConversationCount: migratedConversationMessages?.length || 0, removedUserApiKeys: state.inferenceContext && 'userApiKeys' in state.inferenceContext }); @@ -163,7 +166,6 @@ export class StateMigration { const newState = { ...state, generatedFilesMap: migratedFilesMap, - templateDetails: migratedTemplateDetails, conversationMessages: migratedConversationMessages, inferenceContext: migratedInferenceContext, projectUpdatesAccumulator: [] diff --git a/worker/agents/services/implementations/DeploymentManager.ts b/worker/agents/services/implementations/DeploymentManager.ts index 29988211..7ebe4432 100644 --- a/worker/agents/services/implementations/DeploymentManager.ts +++ b/worker/agents/services/implementations/DeploymentManager.ts @@ -517,7 +517,7 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa */ private async createNewInstance(): Promise { const state = this.getState(); - const templateName = state.templateDetails?.name || 'scratch'; + const templateName = state.templateName || 'scratch'; // Generate unique project name let prefix = (state.blueprint?.projectName || templateName) @@ -532,7 +532,7 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa // Add AI proxy vars if AI template let localEnvVars: Record = {}; - if (state.templateDetails?.name?.includes('agents')) { + if (state.templateName?.includes('agents')) { localEnvVars = { "CF_AI_BASE_URL": generateAppProxyUrl(this.env), "CF_AI_API_KEY": await generateAppProxyToken( diff --git a/worker/agents/services/implementations/FileManager.ts b/worker/agents/services/implementations/FileManager.ts index 60d72bde..7e74c25b 100644 --- a/worker/agents/services/implementations/FileManager.ts +++ b/worker/agents/services/implementations/FileManager.ts @@ -2,9 +2,9 @@ import * as Diff from 'diff'; import { IFileManager } from '../interfaces/IFileManager'; import { IStateManager } from '../interfaces/IStateManager'; import { FileOutputType } from '../../schemas'; -// import { TemplateDetails } from '../../../services/sandbox/sandboxTypes'; import { FileProcessing } from '../../domain/pure/FileProcessing'; import { FileState } from 'worker/agents/core/state'; +import { TemplateDetails } from '../../../services/sandbox/sandboxTypes'; /** * Manages file operations for code generation @@ -12,7 +12,8 @@ import { FileState } from 'worker/agents/core/state'; */ export class FileManager implements IFileManager { constructor( - private stateManager: IStateManager + private stateManager: IStateManager, + private getTemplateDetailsFunc: () => TemplateDetails ) {} getGeneratedFile(path: string): FileOutputType | null { @@ -22,7 +23,7 @@ export class FileManager implements IFileManager { getAllFiles(): FileOutputType[] { const state = this.stateManager.getState(); - return FileProcessing.getAllFiles(state.templateDetails, state.generatedFilesMap); + return FileProcessing.getAllFiles(this.getTemplateDetailsFunc(), state.generatedFilesMap); } saveGeneratedFile(file: FileOutputType): FileState { @@ -86,6 +87,7 @@ export class FileManager implements IFileManager { generatedFilesMap: newFilesMap }); } + fileExists(path: string): boolean { return !!this.getGeneratedFile(path) } diff --git a/worker/api/websocketTypes.ts b/worker/api/websocketTypes.ts index 3208d768..76057985 100644 --- a/worker/api/websocketTypes.ts +++ b/worker/api/websocketTypes.ts @@ -1,7 +1,7 @@ import type { CodeReviewOutputType, FileConceptType, FileOutputType } from "../agents/schemas"; import type { CodeGenState } from "../agents/core/state"; import type { ConversationState } from "../agents/inferutils/common"; -import type { CodeIssue, RuntimeError, StaticAnalysisResponse } from "../services/sandbox/sandboxTypes"; +import type { CodeIssue, RuntimeError, StaticAnalysisResponse, TemplateDetails } from "../services/sandbox/sandboxTypes"; import type { CodeFixResult } from "../services/code-fixer"; import { IssueReport } from "../agents/domain/values/IssueReport"; import type { RateLimitExceededError } from 'shared/types/errors'; @@ -16,6 +16,12 @@ type StateMessage = { state: CodeGenState; }; +type AgentConnectedMessage = { + type: 'agent_connected'; + state: CodeGenState; + templateDetails: TemplateDetails; +}; + type ConversationStateMessage = { type: 'conversation_state'; state: ConversationState; @@ -408,6 +414,7 @@ type ServerLogMessage = { export type WebSocketMessage = | StateMessage + | AgentConnectedMessage | ConversationStateMessage | GenerationStartedMessage | FileGeneratingMessage diff --git a/worker/services/sandbox/BaseSandboxService.ts b/worker/services/sandbox/BaseSandboxService.ts index 63b8eeec..5690df1e 100644 --- a/worker/services/sandbox/BaseSandboxService.ts +++ b/worker/services/sandbox/BaseSandboxService.ts @@ -38,32 +38,34 @@ import { FileOutputType } from 'worker/agents/schemas'; import { ZipExtractor } from './zipExtractor'; import { FileTreeBuilder } from './fileTreeBuilder'; - /** - * Streaming event for enhanced command execution - */ - export interface StreamEvent { +/** + * Streaming event for enhanced command execution + */ +export interface StreamEvent { type: 'stdout' | 'stderr' | 'exit' | 'error'; data?: string; code?: number; error?: string; timestamp: Date; - } +} - export interface TemplateInfo { - name: string; - language?: string; - frameworks?: string[]; - description: { - selection: string; - usage: string; - }; - } +export interface TemplateInfo { + name: string; + language?: string; + frameworks?: string[]; + description: { + selection: string; + usage: string; + }; +} + +const templateDetailsCache: Record = {}; - /** - * Abstract base class providing complete RunnerService API compatibility - * All implementations MUST support every method defined here - */ - export abstract class BaseSandboxService { +/** + * Abstract base class providing complete RunnerService API compatibility + * All implementations MUST support every method defined here +*/ +export abstract class BaseSandboxService { protected logger: StructuredLogger; protected sandboxId: string; @@ -122,6 +124,13 @@ import { FileTreeBuilder } from './fileTreeBuilder'; */ static async getTemplateDetails(templateName: string, downloadDir?: string): Promise { try { + if (templateDetailsCache[templateName]) { + console.log(`Template details for template: ${templateName} found in cache`); + return { + success: true, + templateDetails: templateDetailsCache[templateName] + }; + } // Download template zip from R2 const downloadUrl = downloadDir ? `${downloadDir}/${templateName}.zip` : `${templateName}.zip`; const r2Object = await env.TEMPLATES_BUCKET.get(downloadUrl); @@ -187,6 +196,8 @@ import { FileTreeBuilder } from './fileTreeBuilder'; frameworks: catalogInfo?.frameworks || [] }; + templateDetailsCache[templateName] = templateDetails; + return { success: true, templateDetails @@ -307,4 +318,4 @@ import { FileTreeBuilder } from './fileTreeBuilder'; * Push instance files to existing GitHub repository */ abstract pushToGitHub(instanceId: string, request: GitHubPushRequest, files: FileOutputType[]): Promise - } \ No newline at end of file +} \ No newline at end of file From 2b761d0d3d45d43206212f1166b98fb8dbcf3732 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Mon, 20 Oct 2025 22:08:02 -0400 Subject: [PATCH 050/150] fix: generation context template details passing --- worker/agents/core/simpleGeneratorAgent.ts | 2 +- worker/agents/domain/values/GenerationContext.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index e4bd0af8..de298052 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -445,7 +445,7 @@ export class SimpleCodeGeneratorAgent extends Agent { return { env: this.env, agentId: this.getAgentId(), - context: GenerationContext.from(this.state, this.logger()), + context: GenerationContext.from(this.state, this.getTemplateDetails(), this.logger()), logger: this.logger(), inferenceContext: this.getInferenceContext(), agent: this.codingAgent diff --git a/worker/agents/domain/values/GenerationContext.ts b/worker/agents/domain/values/GenerationContext.ts index 2d7f4014..2fee5aa3 100644 --- a/worker/agents/domain/values/GenerationContext.ts +++ b/worker/agents/domain/values/GenerationContext.ts @@ -30,22 +30,22 @@ export class GenerationContext { /** * Create context from current state */ - static from(state: CodeGenState, logger?: Pick): GenerationContext { + static from(state: CodeGenState, templateDetails: TemplateDetails, logger?: Pick): GenerationContext { const dependencies = DependencyManagement.mergeDependencies( - state.templateDetails?.deps || {}, + templateDetails.deps || {}, state.lastPackageJson, logger ); const allFiles = FileProcessing.getAllFiles( - state.templateDetails, + templateDetails, state.generatedFilesMap ); return new GenerationContext( state.query, state.blueprint, - state.templateDetails, + templateDetails, dependencies, allFiles, state.generatedPhases, From a6c67d01be38e235207a647b83dd5dde0167dd49 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Mon, 20 Oct 2025 23:35:27 -0400 Subject: [PATCH 051/150] perf: optimize binary file detection and base64 encoding in zip extractor --- worker/services/sandbox/zipExtractor.ts | 66 +++++++++++++++++++------ 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/worker/services/sandbox/zipExtractor.ts b/worker/services/sandbox/zipExtractor.ts index b81d5170..e67eba7b 100644 --- a/worker/services/sandbox/zipExtractor.ts +++ b/worker/services/sandbox/zipExtractor.ts @@ -9,6 +9,18 @@ export class ZipExtractor { // Max uncompressed size (50MB) private static readonly MAX_UNCOMPRESSED_SIZE = 50 * 1024 * 1024; + // Known binary file extensions - skip UTF-8 decode attempt + private static readonly BINARY_EXTENSIONS = new Set([ + '.png', '.jpg', '.jpeg', '.gif', '.ico', '.webp', '.bmp', + '.woff', '.woff2', '.ttf', '.otf', '.eot', + '.zip', '.tar', '.gz', '.pdf', + '.mp3', '.mp4', '.webm', '.ogg', + '.bin', '.exe', '.dll', '.so' + ]); + + // TextDecoder + private static readonly utf8Decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: false }); + /** * Extracts all files from a zip archive * @@ -43,21 +55,20 @@ export class ZipExtractor { let fileContents: string; - // Attempt UTF-8 decoding - try { - const decoder = new TextDecoder('utf-8'); - fileContents = decoder.decode(fileData); - - // Replacement character indicates invalid UTF-8 sequence (binary data) - if (fileContents.includes('\uFFFD')) { - throw new Error('Contains replacement characters'); + // Check if file extension is known binary + const isBinary = this.isBinaryExtension(filePath); + + if (isBinary) { + // Skip UTF-8 decode attempt, go straight to base64 + fileContents = `base64:${this.base64Encode(fileData)}`; + } else { + // Attempt UTF-8 decoding with fatal mode (throws on invalid) + try { + fileContents = this.utf8Decoder.decode(fileData); + } catch { + // Binary file detected, encode as base64 + fileContents = `base64:${this.base64Encode(fileData)}`; } - } catch (error) { - // Binary file detected, encode as base64 - const binaryString = Array.from(fileData) - .map(byte => String.fromCharCode(byte)) - .join(''); - fileContents = `base64:${btoa(binaryString)}`; } files.push({ @@ -109,4 +120,31 @@ export class ZipExtractor { return fileContents.startsWith('base64:'); } + /** + * check if file extension is known binary type + */ + private static isBinaryExtension(filePath: string): boolean { + const lastDot = filePath.lastIndexOf('.'); + if (lastDot === -1) return false; + + const ext = filePath.substring(lastDot).toLowerCase(); + return this.BINARY_EXTENSIONS.has(ext); + } + + /** + * base64 encoding + */ + private static base64Encode(data: Uint8Array): string { + let binaryString = ''; + const len = data.length; + + // Process in chunks + const chunkSize = 8192; + for (let i = 0; i < len; i += chunkSize) { + const chunk = data.subarray(i, Math.min(i + chunkSize, len)); + binaryString += String.fromCharCode(...chunk); + } + + return btoa(binaryString); + } } From f76a0b6c4327d0f12ab50e0a5adfdcbb606db2da Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Tue, 21 Oct 2025 00:17:32 -0400 Subject: [PATCH 052/150] refactor: file manager apis to use cached template details for all files --- worker/agents/core/simpleGeneratorAgent.ts | 30 +++-------------- worker/agents/domain/pure/FileProcessing.ts | 16 ++++++--- .../agents/domain/values/GenerationContext.ts | 2 +- .../services/implementations/FileManager.ts | 33 +++++++++++++++++++ .../services/interfaces/IFileManager.ts | 7 +++- 5 files changed, 56 insertions(+), 32 deletions(-) diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index de298052..f375ed5e 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -33,7 +33,7 @@ import { WebSocketMessageData, WebSocketMessageType } from '../../api/websocketT import { InferenceContext, AgentActionKey } from '../inferutils/config.types'; import { AGENT_CONFIG } from '../inferutils/config'; import { ModelConfigService } from '../../database/services/ModelConfigService'; -import { FileFetcher, fixProjectIssues } from '../../services/code-fixer'; +import { fixProjectIssues } from '../../services/code-fixer'; import { FastCodeFixerOperation } from '../operations/PostPhaseCodeFixer'; import { looksLikeCommand } from '../utils/common'; import { generateBlueprint } from '../planning/blueprint'; @@ -1394,7 +1394,7 @@ export class SimpleCodeGeneratorAgent extends Agent { return; } const issues = staticAnalysis.typecheck.issues.concat(staticAnalysis.lint.issues); - const allFiles = this.fileManager.getAllFiles(); + const allFiles = this.fileManager.getAllRelevantFiles(); const fastCodeFixer = await this.operations.fastCodeFixer.execute({ query: this.state.query, @@ -1434,35 +1434,13 @@ export class SimpleCodeGeneratorAgent extends Agent { this.logger().info(`Attempting to fix ${typeCheckIssues.length} TypeScript issues using deterministic code fixer`); const allFiles = this.fileManager.getAllFiles(); - // Create file fetcher callback - const fileFetcher: FileFetcher = async (filePath: string) => { - // Fetch a single file from the instance - try { - const result = await this.getSandboxServiceClient().getFiles(this.state.sandboxInstanceId!, [filePath]); - if (result.success && result.files.length > 0) { - this.logger().info(`Successfully fetched file: ${filePath}`); - return { - filePath: filePath, - fileContents: result.files[0].fileContents, - filePurpose: `Fetched file: ${filePath}` - }; - } else { - this.logger().debug(`File not found: ${filePath}`); - } - } catch (error) { - this.logger().debug(`Failed to fetch file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - return null; - }; - - const fixResult = await fixProjectIssues( + const fixResult = fixProjectIssues( allFiles.map(file => ({ filePath: file.filePath, fileContents: file.fileContents, filePurpose: '' })), - typeCheckIssues, - fileFetcher + typeCheckIssues ); this.broadcast(WebSocketMessageResponses.DETERMINISTIC_CODE_FIX_COMPLETED, { diff --git a/worker/agents/domain/pure/FileProcessing.ts b/worker/agents/domain/pure/FileProcessing.ts index b3b809d7..d900169f 100644 --- a/worker/agents/domain/pure/FileProcessing.ts +++ b/worker/agents/domain/pure/FileProcessing.ts @@ -3,7 +3,7 @@ import type { StructuredLogger } from '../../../logger'; import { TemplateDetails } from '../../../services/sandbox/sandboxTypes'; import { applyUnifiedDiff } from '../../output-formats/diff-formats'; import { FileState } from 'worker/agents/core/state'; -import { getTemplateFiles } from 'worker/services/sandbox/utils'; +import { getTemplateFiles, getTemplateImportantFiles } from 'worker/services/sandbox/utils'; /** * File processing utilities @@ -88,14 +88,22 @@ export class FileProcessing { } /** - * Get all files combining template and generated files + * Get all relevant files combining template (important) and generated files * Template files are overridden by generated files with same path */ - static getAllFiles( + static getAllRelevantFiles( templateDetails: TemplateDetails | undefined, generatedFilesMap: Record ): FileState[] { - const templateFiles = templateDetails?.allFiles ? getTemplateFiles(templateDetails) : []; + return this.getAllFiles(templateDetails, generatedFilesMap, true); + } + + static getAllFiles( + templateDetails: TemplateDetails | undefined, + generatedFilesMap: Record, + onlyImportantFiles: boolean = false + ): FileState[] { + const templateFiles = templateDetails?.allFiles ? (onlyImportantFiles ? getTemplateImportantFiles(templateDetails) : getTemplateFiles(templateDetails)) : []; // Filter out template files that have been overridden by generated files const nonOverriddenTemplateFiles = templateFiles.filter( diff --git a/worker/agents/domain/values/GenerationContext.ts b/worker/agents/domain/values/GenerationContext.ts index 2fee5aa3..39176ab9 100644 --- a/worker/agents/domain/values/GenerationContext.ts +++ b/worker/agents/domain/values/GenerationContext.ts @@ -37,7 +37,7 @@ export class GenerationContext { logger ); - const allFiles = FileProcessing.getAllFiles( + const allFiles = FileProcessing.getAllRelevantFiles( templateDetails, state.generatedFilesMap ); diff --git a/worker/agents/services/implementations/FileManager.ts b/worker/agents/services/implementations/FileManager.ts index 7e74c25b..ef023842 100644 --- a/worker/agents/services/implementations/FileManager.ts +++ b/worker/agents/services/implementations/FileManager.ts @@ -21,6 +21,16 @@ export class FileManager implements IFileManager { return state.generatedFilesMap[path] || null; } + /** + * Get all files combining template and generated files + * Template files are overridden by generated files with same path + * @returns Array of all files. Only returns important template files, not all! + */ + getAllRelevantFiles(): FileOutputType[] { + const state = this.stateManager.getState(); + return FileProcessing.getAllRelevantFiles(this.getTemplateDetailsFunc(), state.generatedFilesMap); + } + getAllFiles(): FileOutputType[] { const state = this.stateManager.getState(); return FileProcessing.getAllFiles(this.getTemplateDetailsFunc(), state.generatedFilesMap); @@ -106,4 +116,27 @@ export class FileManager implements IFileManager { const state = this.stateManager.getState(); return Object.values(state.generatedFilesMap); } + + getTemplateFile(filePath: string) : FileOutputType | null { + const templateDetails = this.getTemplateDetailsFunc(); + const fileContents = templateDetails.allFiles[filePath]; + if (!fileContents) { + return null; + } + return { + filePath, + fileContents, + filePurpose: 'Bootstrapped template file', + } + } + + getFile(filePath: string) : FileOutputType | null { + // First search generated files + const generatedFile = this.getGeneratedFile(filePath); + if (generatedFile) { + return generatedFile; + } + // Then search template files + return this.getTemplateFile(filePath); + } } \ No newline at end of file diff --git a/worker/agents/services/interfaces/IFileManager.ts b/worker/agents/services/interfaces/IFileManager.ts index 58e8e5f1..02b9af01 100644 --- a/worker/agents/services/interfaces/IFileManager.ts +++ b/worker/agents/services/interfaces/IFileManager.ts @@ -12,7 +12,12 @@ export interface IFileManager { getGeneratedFile(path: string): FileOutputType | null; /** - * Get all files (template + generated) + * Get all relevant files (template (important) + generated) + */ + getAllRelevantFiles(): FileOutputType[]; + + /** + * Get all files (template (important) + generated) */ getAllFiles(): FileOutputType[]; From 23f0261d9079c9c2991074180c8b5468af0e085a Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Tue, 21 Oct 2025 00:17:57 -0400 Subject: [PATCH 053/150] feat: make deterministic code fixer completely sync --- worker/services/code-fixer/fixers/ts2304.ts | 11 ++--- worker/services/code-fixer/fixers/ts2305.ts | 6 +-- worker/services/code-fixer/fixers/ts2307.ts | 19 ++++----- worker/services/code-fixer/fixers/ts2613.ts | 18 +++----- worker/services/code-fixer/fixers/ts2614.ts | 15 +++---- worker/services/code-fixer/fixers/ts2724.ts | 22 ++++------ worker/services/code-fixer/index.ts | 19 ++++----- worker/services/code-fixer/types.ts | 10 +---- worker/services/code-fixer/utils/helpers.ts | 32 +++++++------- worker/services/code-fixer/utils/imports.ts | 46 ++++----------------- worker/services/code-fixer/utils/modules.ts | 20 ++++----- worker/services/code-fixer/utils/paths.ts | 24 +++++------ worker/services/code-fixer/utils/stubs.ts | 20 ++++----- worker/services/sandbox/sandboxSdkClient.ts | 25 +---------- 14 files changed, 93 insertions(+), 194 deletions(-) diff --git a/worker/services/code-fixer/fixers/ts2304.ts b/worker/services/code-fixer/fixers/ts2304.ts index 25b8733a..c1de2bd9 100644 --- a/worker/services/code-fixer/fixers/ts2304.ts +++ b/worker/services/code-fixer/fixers/ts2304.ts @@ -14,15 +14,14 @@ import { handleFixerError } from '../utils/helpers'; * Fix TS2304 "Cannot find name" errors * Preserves exact logic from working DeclarationFixer.fixUndefinedName */ -export async function fixUndefinedName( +export function fixUndefinedName( context: FixerContext, issues: CodeIssue[] -): Promise { +): FixResult { const fixedIssues: FixedIssue[] = []; const unfixableIssues: UnfixableIssue[] = []; const modifiedFilesMap = new Map(); const newFiles: FileObject[] = []; - const fetchedFiles = new Set(context.fetchedFiles); // Group issues by file to handle multiple undefined names in the same file const issuesByFile = new Map(); @@ -35,11 +34,9 @@ export async function fixUndefinedName( // Process each file's issues together for (const [filePath, fileIssues] of issuesByFile) { try { - const fileContent = await getFileContent( + const fileContent = getFileContent( filePath, - context.files, - context.fileFetcher, - fetchedFiles + context.files ); if (!fileContent) { diff --git a/worker/services/code-fixer/fixers/ts2305.ts b/worker/services/code-fixer/fixers/ts2305.ts index dc2501c5..3f38e162 100644 --- a/worker/services/code-fixer/fixers/ts2305.ts +++ b/worker/services/code-fixer/fixers/ts2305.ts @@ -24,10 +24,10 @@ const logger = createObjectLogger({ name: 'TS2305Fixer' }, 'TS2305Fixer'); * Fix TS2305 "Module has no exported member" errors * Adds missing exports as stubs to the target file */ -export async function fixMissingExportedMember( +export function fixMissingExportedMember( context: FixerContext, issues: CodeIssue[] -): Promise { +): FixResult { const logs = createFixerLogMessages('TS2305Fixer', issues.length); logger.info(logs.start); @@ -49,7 +49,7 @@ export async function fixMissingExportedMember( } // Get source and target files using DRY helper - const filesResult = await getSourceAndTargetFiles(issue, context); + const filesResult = getSourceAndTargetFiles(issue, context); if (!filesResult) { logger.warn(`Failed to get source and target files for ${issue.filePath}`); unfixableIssues.push(createUnfixableIssue( diff --git a/worker/services/code-fixer/fixers/ts2307.ts b/worker/services/code-fixer/fixers/ts2307.ts index 2a37d550..c1c3c9fb 100644 --- a/worker/services/code-fixer/fixers/ts2307.ts +++ b/worker/services/code-fixer/fixers/ts2307.ts @@ -20,24 +20,23 @@ const logger = createObjectLogger({ name: 'TS2307Fixer' }, 'TS2307Fixer'); * Fix TS2307 "Cannot find module" errors * Preserves exact logic from working ImportExportFixer.fixModuleNotFound */ -export async function fixModuleNotFound( +export function fixModuleNotFound( context: FixerContext, issues: CodeIssue[] -): Promise { +): FixResult { logger.info(`Starting TS2307 fixer with ${issues.length} issues`); const fixedIssues: FixedIssue[] = []; const unfixableIssues = []; const modifiedFiles = []; const newFiles = []; - const fetchedFiles = new Set(context.fetchedFiles); for (const issue of issues) { logger.info(`Processing TS2307 issue: ${issue.message} at ${issue.filePath}:${issue.line}`); try { logger.info(`Getting AST for file: ${issue.filePath}`); - const ast = await getFileAST(issue.filePath, context.files, context.fileFetcher, fetchedFiles); + const ast = getFileAST(issue.filePath, context.files); if (!ast) { logger.warn(`Failed to get AST for ${issue.filePath}`); unfixableIssues.push({ @@ -81,12 +80,10 @@ export async function fixModuleNotFound( logger.info(`Searching for local module file: ${moduleSpecifier}`); // Try to find existing file with fuzzy matching - const foundFile = await findModuleFile( + const foundFile = findModuleFile( moduleSpecifier, issue.filePath, - context.files, - context.fileFetcher, - fetchedFiles + context.files ); logger.info(`Module file search result: ${foundFile || 'NOT FOUND'}`); @@ -124,11 +121,9 @@ export async function fixModuleNotFound( logger.info(`Resolved stub file path: "${targetFilePath}"`); logger.info(`Generating stub content for import: ${importInfo.defaultImport ? 'default: ' + importInfo.defaultImport + ', ' : ''}named: [${importInfo.namedImports.join(', ')}]`); - const stubContent = await generateStubFileContent( + const stubContent = generateStubFileContent( importInfo, - context.files, - context.fileFetcher, - fetchedFiles + context.files ); logger.info(`Generated stub content (${stubContent.length} characters)`); diff --git a/worker/services/code-fixer/fixers/ts2613.ts b/worker/services/code-fixer/fixers/ts2613.ts index dba6fcf3..77ceeaed 100644 --- a/worker/services/code-fixer/fixers/ts2613.ts +++ b/worker/services/code-fixer/fixers/ts2613.ts @@ -19,24 +19,23 @@ const logger = createObjectLogger({ name: 'TS2613Fixer' }, 'TS2613Fixer'); * Fix TS2613 "Module is not a module" errors * Preserves exact logic from working ImportExportFixer.fixModuleIsNotModule */ -export async function fixModuleIsNotModule( +export function fixModuleIsNotModule( context: FixerContext, issues: CodeIssue[] -): Promise { +): FixResult { logger.info(`Starting TS2613 fixer with ${issues.length} issues`); const fixedIssues: FixedIssue[] = []; const unfixableIssues: UnfixableIssue[] = []; const modifiedFiles: FileObject[] = []; const newFiles: FileObject[] = []; - const fetchedFiles = new Set(context.fetchedFiles); for (const issue of issues) { logger.info(`Processing TS2613 issue: ${issue.message} at ${issue.filePath}:${issue.line}`); try { logger.info(`Getting AST for file: ${issue.filePath}`); - const ast = await getFileAST(issue.filePath, context.files, context.fileFetcher, fetchedFiles); + const ast = getFileAST(issue.filePath, context.files); if (!ast) { logger.warn(`Failed to get AST for ${issue.filePath}`); unfixableIssues.push({ @@ -71,12 +70,10 @@ export async function fixModuleIsNotModule( const moduleSpecifier = importInfo ? importInfo.moduleSpecifier : (namespaceImport?.moduleSpecifier || ''); logger.info(`Searching for target file: ${moduleSpecifier}`); - const targetFile = await findModuleFile( + const targetFile = findModuleFile( moduleSpecifier, issue.filePath, - context.files, - context.fileFetcher, - fetchedFiles + context.files ); if (!targetFile) { @@ -94,10 +91,7 @@ export async function fixModuleIsNotModule( logger.info(`Found target file: ${targetFile}`); logger.info(`Getting AST for target file: ${targetFile}`); - logger.info(`Files in context: ${Array.from(context.files.keys()).join(', ')}`); - logger.info(`FetchedFiles: ${Array.from(fetchedFiles).join(', ')}`); - logger.info(`FileFetcher available: ${!!context.fileFetcher}`); - const targetAST = await getFileAST(targetFile, context.files, context.fileFetcher, fetchedFiles); + const targetAST = getFileAST(targetFile, context.files); logger.info(`getFileAST result for ${targetFile}: ${!!targetAST}`); if (!targetAST) { logger.warn(`Failed to parse target file: ${targetFile}`); diff --git a/worker/services/code-fixer/fixers/ts2614.ts b/worker/services/code-fixer/fixers/ts2614.ts index 41b71076..880ac143 100644 --- a/worker/services/code-fixer/fixers/ts2614.ts +++ b/worker/services/code-fixer/fixers/ts2614.ts @@ -18,17 +18,16 @@ const logger = createObjectLogger({ name: 'TS2614Fixer' }, 'TS2614Fixer'); * Fix TS2614 "Module has no exported member" errors (import/export mismatch) * Corrects import statements to match actual export types */ -export async function fixImportExportTypeMismatch( +export function fixImportExportTypeMismatch( context: FixerContext, issues: CodeIssue[] -): Promise { +): FixResult { logger.info(`Starting TS2614 fixer with ${issues.length} issues`); const fixedIssues: FixedIssue[] = []; const unfixableIssues: UnfixableIssue[] = []; const modifiedFiles: FileObject[] = []; const newFiles: FileObject[] = []; - const fetchedFiles = new Set(context.fetchedFiles); for (const issue of issues) { logger.info(`Processing TS2614 issue: ${issue.message} at ${issue.filePath}:${issue.line}`); @@ -36,7 +35,7 @@ export async function fixImportExportTypeMismatch( try { // Get AST for the file with the import issue logger.info(`Getting AST for source file: ${issue.filePath}`); - const sourceAST = await getFileAST(issue.filePath, context.files, context.fileFetcher, fetchedFiles); + const sourceAST = getFileAST(issue.filePath, context.files); if (!sourceAST) { logger.warn(`Failed to get AST for ${issue.filePath}`); unfixableIssues.push({ @@ -70,12 +69,10 @@ export async function fixImportExportTypeMismatch( // Find the target file logger.info(`Searching for target file: ${importInfo.moduleSpecifier}`); - const targetFile = await findModuleFile( + const targetFile = findModuleFile( importInfo.moduleSpecifier, issue.filePath, - context.files, - context.fileFetcher, - fetchedFiles + context.files ); if (!targetFile) { @@ -94,7 +91,7 @@ export async function fixImportExportTypeMismatch( // Get AST for target file to analyze actual exports logger.info(`Getting AST for target file: ${targetFile}`); - const targetAST = await getFileAST(targetFile, context.files, context.fileFetcher, fetchedFiles); + const targetAST = getFileAST(targetFile, context.files); if (!targetAST) { logger.warn(`Failed to parse target file: ${targetFile}`); unfixableIssues.push({ diff --git a/worker/services/code-fixer/fixers/ts2724.ts b/worker/services/code-fixer/fixers/ts2724.ts index bf8f6058..31114064 100644 --- a/worker/services/code-fixer/fixers/ts2724.ts +++ b/worker/services/code-fixer/fixers/ts2724.ts @@ -27,10 +27,10 @@ const logger = createObjectLogger({ name: 'TS2724Fixer' }, 'TS2724Fixer'); * Fix TS2724 "Incorrect named import" errors * Replaces incorrect named imports with the suggested correct ones from TypeScript */ -export async function fixIncorrectNamedImport( +export function fixIncorrectNamedImport( context: FixerContext, issues: CodeIssue[] -): Promise { +): FixResult { const logs = createFixerLogMessages('TS2724Fixer', issues.length); logger.info(logs.start); @@ -51,11 +51,9 @@ export async function fixIncorrectNamedImport( for (const [filePath, fileIssues] of issuesByFile) { try { // Get source file AST once - const sourceAST = await getFileAST( + const sourceAST = getFileAST( filePath, - context.files, - context.fileFetcher, - context.fetchedFiles as Set + context.files ); if (!sourceAST) { @@ -114,22 +112,18 @@ export async function fixIncorrectNamedImport( // Verify the suggested export actually exists in the target module // Resolve the module path const resolvedPath = resolvePathAlias(moduleSpecifier); - const targetFile = await findModuleFile( + const targetFile = findModuleFile( resolvedPath, filePath, - context.files, - context.fileFetcher, - context.fetchedFiles as Set + context.files ); if (targetFile) { // Get exports from the target file - const targetAST = await getFileAST( + const targetAST = getFileAST( targetFile, - context.files, - context.fileFetcher, - context.fetchedFiles as Set + context.files ); if (targetAST) { diff --git a/worker/services/code-fixer/index.ts b/worker/services/code-fixer/index.ts index 2cfd82d1..033f990a 100644 --- a/worker/services/code-fixer/index.ts +++ b/worker/services/code-fixer/index.ts @@ -7,7 +7,6 @@ import { FileObject } from './types'; import { CodeIssue } from '../sandbox/sandboxTypes'; import { CodeFixResult, - FileFetcher, FixerContext, FileMap, ProjectFile, @@ -35,14 +34,12 @@ import { fixIncorrectNamedImport } from './fixers/ts2724'; * * @param allFiles - Initial files to work with * @param issues - TypeScript compilation issues to fix - * @param fileFetcher - Optional callback to fetch additional files on-demand * @returns Promise containing fix results with modified/new files */ -export async function fixProjectIssues( +export function fixProjectIssues( allFiles: FileObject[], - issues: CodeIssue[], - fileFetcher?: FileFetcher -): Promise { + issues: CodeIssue[] +): CodeFixResult { try { // Build file map (mutable for caching fetched files) const fileMap = createFileMap(allFiles); @@ -51,7 +48,6 @@ export async function fixProjectIssues( const fetchedFiles = new Set(); const context: FixerContext = { files: fileMap, - fileFetcher, fetchedFiles }; @@ -65,7 +61,7 @@ export async function fixProjectIssues( const sortedIssues = sortFixOrder(fixableIssues); // Apply fixes sequentially, updating context after each - const results = await applyFixesSequentially( + const results = applyFixesSequentially( context, sortedIssues, fixerRegistry @@ -209,11 +205,11 @@ function sortFixOrder(issues: CodeIssue[]): CodeIssue[] { /** * Apply fixes sequentially, updating context after each fix */ -async function applyFixesSequentially( +function applyFixesSequentially( context: FixerContext, sortedIssues: CodeIssue[], fixerRegistry: FixerRegistry -): Promise { +): CodeFixResult { const fixedIssues: any[] = []; const unfixableIssues: any[] = []; const modifiedFiles = new Map(); @@ -259,7 +255,7 @@ async function applyFixesSequentially( try { // Apply fixer - const result = await fixer(context, issues); + const result = fixer(context, issues); // Collect results fixedIssues.push(...result.fixedIssues); @@ -354,7 +350,6 @@ export type { FixedIssue, UnfixableIssue, FileObject, - FileFetcher, FixerContext, FileMap, ProjectFile diff --git a/worker/services/code-fixer/types.ts b/worker/services/code-fixer/types.ts index 72db5ad4..c1e9b249 100644 --- a/worker/services/code-fixer/types.ts +++ b/worker/services/code-fixer/types.ts @@ -70,12 +70,6 @@ export interface CodeFixResult { // ============================================================================ // FILE AND AST MANAGEMENT // ============================================================================ - -/** - * File fetcher callback type for dynamically loading files not in the initial set - */ -export type FileFetcher = (filePath: string) => Promise; - /** * Represents a file in the project with its content and cached AST */ @@ -96,8 +90,6 @@ export type FileMap = Map; export interface FixerContext { /** Map of all files in the project (mutable for caching fetched files) */ files: FileMap; - /** Optional callback to fetch additional files */ - readonly fileFetcher?: FileFetcher; /** Cache of fetched files to prevent duplicate requests */ readonly fetchedFiles: ReadonlySet; } @@ -174,7 +166,7 @@ export interface FixResult { export type FixerFunction = ( context: FixerContext, issues: CodeIssue[] -) => Promise; +) => FixResult; /** * Registry of fixer functions by issue code diff --git a/worker/services/code-fixer/utils/helpers.ts b/worker/services/code-fixer/utils/helpers.ts index e27aafd0..88ae6590 100644 --- a/worker/services/code-fixer/utils/helpers.ts +++ b/worker/services/code-fixer/utils/helpers.ts @@ -17,19 +17,17 @@ import { resolveModuleFile, validateModuleOperation } from './modules'; * Standard pattern: Get source file AST and import info * Used by TS2305, TS2613, TS2614 fixers */ -export async function getSourceFileAndImport( +export function getSourceFileAndImport( issue: CodeIssue, context: FixerContext -): Promise<{ +): { sourceAST: t.File; importInfo: { moduleSpecifier: string; defaultImport?: string; namedImports: string[]; specifier?: string }; -} | null> { +} | null { // Get AST for the source file - const sourceAST = await getFileAST( + const sourceAST = getFileAST( issue.filePath, context.files, - context.fileFetcher, - context.fetchedFiles as Set ); if (!sourceAST) { @@ -49,14 +47,14 @@ export async function getSourceFileAndImport( * Standard pattern: Get target file for a module specifier * Used by TS2305, TS2613, TS2614 fixers */ -export async function getTargetFileAndAST( +export function getTargetFileAndAST( moduleSpecifier: string, fromFilePath: string, context: FixerContext -): Promise<{ +): { targetFilePath: string; targetAST: t.File; -} | null> { +} | null { // Validate the module operation first const validation = validateModuleOperation(moduleSpecifier, null); if (!validation.valid) { @@ -64,7 +62,7 @@ export async function getTargetFileAndAST( } // Resolve the target file - const targetFilePath = await resolveModuleFile(moduleSpecifier, fromFilePath, context); + const targetFilePath = resolveModuleFile(moduleSpecifier, fromFilePath, context); if (!targetFilePath) { return null; } @@ -76,11 +74,9 @@ export async function getTargetFileAndAST( } // Get AST for target file - const targetAST = await getFileAST( + const targetAST = getFileAST( targetFilePath, context.files, - context.fileFetcher, - context.fetchedFiles as Set ); if (!targetAST) { @@ -94,17 +90,17 @@ export async function getTargetFileAndAST( * Combined pattern: Get both source and target files * Used by import/export fixers that need both files */ -export async function getSourceAndTargetFiles( +export function getSourceAndTargetFiles( issue: CodeIssue, context: FixerContext -): Promise<{ +): { sourceAST: t.File; importInfo: { moduleSpecifier: string; defaultImport?: string; namedImports: string[]; specifier?: string }; targetFilePath: string; targetAST: t.File; -} | null> { +} | null { // Get source file and import info - const sourceResult = await getSourceFileAndImport(issue, context); + const sourceResult = getSourceFileAndImport(issue, context); if (!sourceResult) { return null; } @@ -112,7 +108,7 @@ export async function getSourceAndTargetFiles( const { sourceAST, importInfo } = sourceResult; // Get target file and AST - const targetResult = await getTargetFileAndAST( + const targetResult = getTargetFileAndAST( importInfo.moduleSpecifier, issue.filePath, context diff --git a/worker/services/code-fixer/utils/imports.ts b/worker/services/code-fixer/utils/imports.ts index 896d7cb6..4ca1dae7 100644 --- a/worker/services/code-fixer/utils/imports.ts +++ b/worker/services/code-fixer/utils/imports.ts @@ -4,8 +4,8 @@ */ import * as t from '@babel/types'; -import { ImportInfo, ExportInfo, ImportUsage, FileMap, FileFetcher } from '../types'; -import { parseCode, traverseAST, isScriptFile } from './ast'; +import { ImportInfo, ExportInfo, ImportUsage, FileMap } from '../types'; +import { parseCode, traverseAST } from './ast'; import { createObjectLogger } from '../../../logger'; const logger = createObjectLogger({ name: 'ImportUtils' }, 'ImportUtils'); @@ -371,12 +371,10 @@ export function analyzeNameUsage(ast: t.File, name: string): ImportUsage | null /** * Get file content from FileMap or fetch it if not available */ -export async function getFileContent( +export function getFileContent( filePath: string, files: FileMap, - fileFetcher?: FileFetcher, - fetchedFiles?: Set -): Promise { +): string | null { logger.info(`ImportUtils: Getting content for file: ${filePath}`); const file = files.get(filePath); @@ -384,32 +382,8 @@ export async function getFileContent( logger.info(`ImportUtils: Found file in context: ${filePath}`); return file.content; } - - // Try to fetch if not available and we have a fetcher - if (fileFetcher && fetchedFiles && !fetchedFiles.has(filePath)) { - try { - logger.info(`ImportUtils: Fetching file: ${filePath}`); - fetchedFiles.add(filePath); // Mark as attempted - const result = await fileFetcher(filePath); - - if (result && isScriptFile(result.filePath)) { - logger.info(`ImportUtils: Successfully fetched ${filePath}, storing in files map`); - // Store the fetched file in the mutable files map - files.set(filePath, { - filePath: filePath, - content: result.fileContents, - ast: undefined - }); - return result.fileContents; - } else { - logger.info(`ImportUtils: File ${filePath} was fetched but is not a script file or result is null`); - } - } catch (error) { - logger.warn(`ImportUtils: Failed to fetch file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - } else { - logger.info(`ImportUtils: Not fetching ${filePath} - fileFetcher: ${!!fileFetcher}, fetchedFiles: ${!!fetchedFiles}, alreadyFetched: ${fetchedFiles?.has(filePath)}`); - } + + logger.info(`ImportUtils: File not found in context: ${filePath}`); return null; } @@ -417,12 +391,10 @@ export async function getFileContent( /** * Get file AST from FileMap with caching, or parse it if needed */ -export async function getFileAST( +export function getFileAST( filePath: string, files: FileMap, - fileFetcher?: FileFetcher, - fetchedFiles?: Set -): Promise { +): t.File | null { logger.info(`ImportUtils: Getting AST for file: ${filePath}`); const file = files.get(filePath); @@ -432,7 +404,7 @@ export async function getFileAST( return file.ast; } - const content = await getFileContent(filePath, files, fileFetcher, fetchedFiles); + const content = getFileContent(filePath, files); if (!content) { logger.info(`ImportUtils: No content available for ${filePath}`); return null; diff --git a/worker/services/code-fixer/utils/modules.ts b/worker/services/code-fixer/utils/modules.ts index 2248be62..699c97a7 100644 --- a/worker/services/code-fixer/utils/modules.ts +++ b/worker/services/code-fixer/utils/modules.ts @@ -59,43 +59,39 @@ export function canModifyFile(filePath: string): boolean { * Resolve a module specifier to an actual file path within the project * Unified resolution logic used by all fixers */ -export async function resolveModuleFile( +export function resolveModuleFile( moduleSpecifier: string, fromFilePath: string, context: FixerContext -): Promise { +): string | null { // Skip external modules - we cannot modify them if (isExternalModule(moduleSpecifier)) { return null; } // Use existing findModuleFile logic for internal modules - return await findModuleFile( + return findModuleFile( moduleSpecifier, fromFilePath, - context.files, - context.fileFetcher, - context.fetchedFiles as Set + context.files ); } /** * Check if a target file exists and can be modified */ -export async function canModifyTargetFile( +export function canModifyTargetFile( targetFilePath: string, context: FixerContext -): Promise { +): boolean { if (!canModifyFile(targetFilePath)) { return false; } try { - const content = await getFileContent( + const content = getFileContent( targetFilePath, - context.files, - context.fileFetcher, - context.fetchedFiles as Set + context.files ); return content !== null; } catch { diff --git a/worker/services/code-fixer/utils/paths.ts b/worker/services/code-fixer/utils/paths.ts index 7fb6be38..be4c6ba1 100644 --- a/worker/services/code-fixer/utils/paths.ts +++ b/worker/services/code-fixer/utils/paths.ts @@ -3,7 +3,7 @@ * Extracted from working ImportExportAnalyzer to preserve exact functionality */ -import { FileMap, FileFetcher } from '../types'; +import { FileMap } from '../types'; import { isScriptFile } from './ast'; import { getFileContent } from './imports'; @@ -31,13 +31,11 @@ export function resolvePathAlias(importSpecifier: string): string { * Resolve relative import paths to absolute paths within the project * Preserves exact logic from working implementation */ -export async function resolveImportPath( +export function resolveImportPath( importSpecifier: string, currentFilePath: string, - files: FileMap, - fileFetcher?: FileFetcher, - fetchedFiles?: Set -): Promise { + files: FileMap +): string { if (importSpecifier.startsWith('./') || importSpecifier.startsWith('../')) { // Relative import - resolve relative to current file directory const currentDirParts = currentFilePath.split('/').slice(0, -1); @@ -63,7 +61,7 @@ export async function resolveImportPath( if (!resolvedPath.endsWith(ext)) { const withExt = resolvedPath + ext; try { - const fileContent = await getFileContent(withExt, files, fileFetcher, fetchedFiles); + const fileContent = getFileContent(withExt, files); if (fileContent) return withExt; } catch { // File doesn't exist or can't be fetched, try next extension @@ -86,18 +84,16 @@ export async function resolveImportPath( * Find a module file using fuzzy matching and file fetching * Preserves exact logic from working ImportExportAnalyzer.findModuleFile */ -export async function findModuleFile( +export function findModuleFile( importSpecifier: string, currentFilePath: string, - files: FileMap, - fileFetcher?: FileFetcher, - fetchedFiles?: Set -): Promise { + files: FileMap +): string | null { // Handle path aliases like @/components/ui/button const resolvedSpecifier = resolvePathAlias(importSpecifier); // Try exact match first (relative/absolute paths) - const exactMatch = await resolveImportPath(resolvedSpecifier, currentFilePath, files, fileFetcher, fetchedFiles); + const exactMatch = resolveImportPath(resolvedSpecifier, currentFilePath, files); if (exactMatch) { // Check if file exists in files Map after potential fetching const allFiles = Array.from(files.keys()); @@ -127,7 +123,7 @@ export async function findModuleFile( try { // Try to get file content (this will trigger fetching if available) - const content = await getFileContent(candidatePath, files, fileFetcher, fetchedFiles); + const content = getFileContent(candidatePath, files); if (content) { return candidatePath; } diff --git a/worker/services/code-fixer/utils/stubs.ts b/worker/services/code-fixer/utils/stubs.ts index 2c91a6fa..c5e6da4b 100644 --- a/worker/services/code-fixer/utils/stubs.ts +++ b/worker/services/code-fixer/utils/stubs.ts @@ -5,7 +5,7 @@ */ import * as t from '@babel/types'; -import { ImportInfo, ImportUsage, FileMap, FileFetcher } from '../types'; +import { ImportInfo, ImportUsage, FileMap } from '../types'; import { createFileAST, shouldUseJSXExtension, generateCode, parseCode } from './ast'; import { analyzeImportUsage, getFileAST } from './imports'; @@ -17,13 +17,11 @@ import { analyzeImportUsage, getFileAST } from './imports'; * Analyze how imports are used to generate appropriate stubs * Preserves exact logic from working implementation */ -export async function analyzeImportUsageForStub( +export function analyzeImportUsageForStub( importInfo: ImportInfo, - files: FileMap, - fileFetcher?: FileFetcher, - fetchedFiles?: Set -): Promise { - const sourceAST = await getFileAST(importInfo.filePath, files, fileFetcher, fetchedFiles); + files: FileMap +): ImportUsage[] { + const sourceAST = getFileAST(importInfo.filePath, files); if (!sourceAST) return []; const importNames = [ @@ -94,13 +92,11 @@ export function generateStubFileAST( /** * Generate stub file content as a string */ -export async function generateStubFileContent( +export function generateStubFileContent( importInfo: ImportInfo, files: FileMap, - fileFetcher?: FileFetcher, - fetchedFiles?: Set -): Promise { - const usageAnalysis = await analyzeImportUsageForStub(importInfo, files, fileFetcher, fetchedFiles); +): string { + const usageAnalysis = analyzeImportUsageForStub(importInfo, files); const stubAST = generateStubFileAST(importInfo, usageAnalysis); const generated = generateCode(stubAST); diff --git a/worker/services/sandbox/sandboxSdkClient.ts b/worker/services/sandbox/sandboxSdkClient.ts index 292196ce..d0638222 100644 --- a/worker/services/sandbox/sandboxSdkClient.ts +++ b/worker/services/sandbox/sandboxSdkClient.ts @@ -37,7 +37,7 @@ import { import { createAssetManifest } from '../deployer/utils/index'; -import { CodeFixResult, FileFetcher, fixProjectIssues } from '../code-fixer'; +import { CodeFixResult, fixProjectIssues } from '../code-fixer'; import { FileObject } from '../code-fixer/types'; import { generateId } from '../../utils/idGenerator'; import { ResourceProvisioner } from './resourceProvisioner'; @@ -1716,35 +1716,14 @@ export class SandboxSdkClient extends BaseSandboxService { // Create file fetcher callback const session = await this.getInstanceSession(instanceId); - const fileFetcher: FileFetcher = async (filePath: string) => { - // Fetch a single file from the instance - try { - const result = await session.readFile(`/workspace/${instanceId}/${filePath}`); - if (result.success) { - this.logger.info(`Successfully fetched file: ${filePath}`); - return { - filePath: filePath, - fileContents: result.content, - filePurpose: `Fetched file: ${filePath}` - }; - } else { - this.logger.debug(`File not found: ${filePath}`); - } - } catch (error) { - this.logger.debug(`Failed to fetch file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - return null; - }; - // Use the new functional API - const fixResult = await fixProjectIssues( + const fixResult = fixProjectIssues( files.map(file => ({ filePath: file.filePath, fileContents: file.fileContents, filePurpose: '' })), analysisResult.typecheck.issues, - fileFetcher ); for (const file of fixResult.modifiedFiles) { await session.writeFile(`/workspace/${instanceId}/${file.filePath}`, file.fileContents); From d9c7a30732b5dcc5baf6c39635332ebe1030f9cd Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Wed, 22 Oct 2025 02:23:17 -0400 Subject: [PATCH 054/150] fix: use templateName check instead of isInitialized() for agent start validation --- worker/agents/core/simpleGeneratorAgent.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index f375ed5e..90915f28 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -122,7 +122,7 @@ export class SimpleCodeGeneratorAgent extends Agent { } getAgentId() { - return this.state.inferenceContext.agentId + return this.state.inferenceContext.agentId; } initialState: CodeGenState = { @@ -261,11 +261,11 @@ export class SimpleCodeGeneratorAgent extends Agent { async onStart(_props?: Record | undefined): Promise { this.logger().info(`Agent ${this.getAgentId()} session: ${this.state.sessionId} onStart`); // Ignore if agent not initialized - if (!this.isInitialized()) { + if (!this.state.templateName?.trim()) { this.logger().info(`Agent ${this.getAgentId()} session: ${this.state.sessionId} not initialized, ignoring onStart`); return; } - this.logger().info(`Agent ${this.getAgentId()} session: ${this.state.sessionId} onStart being processed`); + this.logger().info(`Agent ${this.getAgentId()} session: ${this.state.sessionId} onStart being processed, template name: ${this.state.templateName}`); // Fill the template cache await this.ensureTemplateDetails(); this.logger().info(`Agent ${this.getAgentId()} session: ${this.state.sessionId} onStart processed successfully`); From d3d47769a7331523c2e2cb4c07b5408e3b7c862f Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Wed, 22 Oct 2025 02:48:46 -0400 Subject: [PATCH 055/150] fix: Fixed filtering important files --- worker/agents/planning/blueprint.ts | 4 ++-- worker/services/sandbox/utils.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/worker/agents/planning/blueprint.ts b/worker/agents/planning/blueprint.ts index 726320f7..5f3148f9 100644 --- a/worker/agents/planning/blueprint.ts +++ b/worker/agents/planning/blueprint.ts @@ -9,7 +9,7 @@ import { TemplateRegistry } from '../inferutils/schemaFormatters'; import z from 'zod'; import { imagesToBase64 } from 'worker/utils/images'; import { ProcessedImageAttachment } from 'worker/types/image-attachment'; -import { getTemplateFiles } from 'worker/services/sandbox/utils'; +import { getTemplateImportantFiles } from 'worker/services/sandbox/utils'; const logger = createLogger('Blueprint'); @@ -188,7 +188,7 @@ export async function generateBlueprint({ env, inferenceContext, query, language // --------------------------------------------------------------------------- const filesText = TemplateRegistry.markdown.serialize( - { files: getTemplateFiles(templateDetails).filter(f => !f.filePath.includes('package.json')) }, + { files: getTemplateImportantFiles(templateDetails).filter(f => !f.filePath.includes('package.json')) }, z.object({ files: z.array(TemplateFileSchema) }) ); diff --git a/worker/services/sandbox/utils.ts b/worker/services/sandbox/utils.ts index 18bd6519..4ff4e248 100644 --- a/worker/services/sandbox/utils.ts +++ b/worker/services/sandbox/utils.ts @@ -1,10 +1,10 @@ import { TemplateDetails, TemplateFile } from "./sandboxTypes"; -export function getTemplateImportantFiles(templateDetails: TemplateDetails): TemplateFile[] { +export function getTemplateImportantFiles(templateDetails: TemplateDetails, filterRedacted: boolean = true): TemplateFile[] { return templateDetails.importantFiles.map(filePath => ({ filePath, - fileContents: templateDetails.allFiles[filePath], - })); + fileContents: filterRedacted && templateDetails.redactedFiles.includes(filePath) ? 'REDACTED' : templateDetails.allFiles[filePath] + })).filter(f => f.fileContents); } export function getTemplateFiles(templateDetails: TemplateDetails): TemplateFile[] { From 6a4887103e388784def70ee12b15c916ee2b3eb2 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Wed, 22 Oct 2025 02:49:06 -0400 Subject: [PATCH 056/150] fix: fixed setup project broken --- worker/agents/assistants/projectsetup.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/worker/agents/assistants/projectsetup.ts b/worker/agents/assistants/projectsetup.ts index 534c816c..f261b3d1 100644 --- a/worker/agents/assistants/projectsetup.ts +++ b/worker/agents/assistants/projectsetup.ts @@ -127,15 +127,10 @@ ${error}`); context: this.inferenceContext, modelName: error? AIModels.GEMINI_2_5_FLASH : undefined, }); - if (!results || typeof results !== 'string') { - this.logger.info(`Failed to generate setup commands, results: `, { results }); - return { commands: [] }; - } - this.logger.info(`Generated setup commands: ${results}`); - this.save([createAssistantMessage(results)]); - return { commands: extractCommands(results) }; + this.save([createAssistantMessage(results.string)]); + return { commands: extractCommands(results.string) }; } catch (error) { this.logger.error("Error generating setup commands:", error); throw error; From 888e934951dee08488ca681e9e6217a877310790 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 23 Oct 2025 15:12:26 -0400 Subject: [PATCH 057/150] chore: update sandbox-sdk version to 0.4.7 --- SandboxDockerfile | 2 +- bun.lock | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SandboxDockerfile b/SandboxDockerfile index 10029ae4..dcbbce33 100644 --- a/SandboxDockerfile +++ b/SandboxDockerfile @@ -1,5 +1,5 @@ # FROM docker.io/cloudflare/sandbox:0.1.3 -FROM docker.io/cloudflare/sandbox:0.4.3 +FROM docker.io/cloudflare/sandbox:0.4.7 ARG TARGETARCH RUN apt-get update && \ diff --git a/bun.lock b/bun.lock index ed18fc2f..a90388e1 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "name": "vibesdk", "dependencies": { "@cloudflare/containers": "^0.0.28", - "@cloudflare/sandbox": "0.4.3", + "@cloudflare/sandbox": "^0.4.7", "@noble/ciphers": "^1.3.0", "@octokit/rest": "^22.0.0", "@radix-ui/react-accordion": "^1.2.12", @@ -222,7 +222,7 @@ "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="], - "@cloudflare/sandbox": ["@cloudflare/sandbox@0.4.3", "", { "dependencies": { "@cloudflare/containers": "^0.0.28" } }, "sha512-ODynUVKLID9viGLDDnFpWXNToy4vvN2WCQliJiWvtgBee8K1WiRDI3SYzUHFt3onV8kBHKjkoCbADQHOPBAllA=="], + "@cloudflare/sandbox": ["@cloudflare/sandbox@0.4.7", "", { "dependencies": { "@cloudflare/containers": "^0.0.28" } }, "sha512-kBnPJMecb9NyMY0ATHLIMXpCM75h5CGmpa6q95cqGOuj0Gp53HojwC9aX4UVwa+jY7WGos08oE9LRQRokX77BA=="], "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.7.5", "", { "peerDependencies": { "unenv": "2.0.0-rc.21", "workerd": "^1.20250924.0" }, "optionalPeers": ["workerd"] }, "sha512-eB3UAIVhrvY+CMZrRXS/bAv5kWdNiH+dgwu+1M1S7keDeonxkfKIGVIrhcCLTkcqYlN30MPURPuVFUEzIWuuvg=="], diff --git a/package.json b/package.json index 21c52674..342ed69d 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ }, "dependencies": { "@cloudflare/containers": "^0.0.28", - "@cloudflare/sandbox": "0.4.3", + "@cloudflare/sandbox": "0.4.7", "@noble/ciphers": "^1.3.0", "@octokit/rest": "^22.0.0", "@radix-ui/react-accordion": "^1.2.12", From ff604c6fafa42be76431d3dd8038edc9b4a71aa5 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 23 Oct 2025 15:12:52 -0400 Subject: [PATCH 058/150] feat: prompt refinements for zustand --- .../agents/operations/PhaseImplementation.ts | 27 ++++++++-- worker/agents/prompts.ts | 51 ++++++++++--------- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/worker/agents/operations/PhaseImplementation.ts b/worker/agents/operations/PhaseImplementation.ts index f2bbb959..4de9f8ba 100644 --- a/worker/agents/operations/PhaseImplementation.ts +++ b/worker/agents/operations/PhaseImplementation.ts @@ -108,6 +108,23 @@ These are the only dependencies, components and plugins available for the projec const USER_PROMPT = `**Phase Implementation** + +⚠️⚠️⚠️ SCAN YOUR MENTAL CODE DRAFT FOR THESE PATTERNS BEFORE WRITING ⚠️⚠️⚠️ + +🔍 FORBIDDEN ZUSTAND PATTERNS (auto-fail if found): + • useStore(s => ({ ... })) ← Creating object in selector + • useStore() ← No selector + • useStore(s => s.getItems()) ← Calling methods + • const { a, b } = useStore(...) ← Destructuring multiple values + • useStore(useShallow) + +✅ ONLY ALLOWED ZUSTAND PATTERN: + • useStore(s => s.singlePrimitive) ← One primitive per call + • Call useStore multiple times for multiple values + +If you find forbidden patterns in your draft, STOP and rewrite before generating files. + + These are the instructions and quality standards that must be followed to implement this phase. **CRITICAL ERROR PREVENTION (Fix These First):** @@ -117,10 +134,12 @@ These are the instructions and quality standards that must be followed to implem - Never call setState during render phase - Always use dependency arrays in useEffect with conditional guards - Stabilize object/array references with useMemo/useCallback - - **Zustand: Select ONLY primitives individually** - - Never use the store without a selector (selecting whole state is forbidden) - - Do not allocate objects/arrays or call store methods inside selectors - - If selecting an object, only store-owned and stable with shallow equality (no allocation) + - **🔥 ZUSTAND ABSOLUTE RULE - NO EXCEPTIONS 🔥** + • ONLY ALLOWED: useStore(s => s.primitive) - one primitive per call + • BANNED: useStore(s => ({ ... })) - object literals crash the app + • BANNED: useStore() - no selector crashes the app + • BANNED: useStore(s => s.getXxx()) - method calls crash the app + • For multiple values: Call useStore multiple times (this is correct and efficient) - DOM listeners must be stable: attach once; read store values via refs; avoid reattaching per state change 2. **Variable Declaration Order** - CRITICAL diff --git a/worker/agents/prompts.ts b/worker/agents/prompts.ts index 2d900b84..d884538d 100644 --- a/worker/agents/prompts.ts +++ b/worker/agents/prompts.ts @@ -562,54 +562,57 @@ const value = useMemo(() => ({ user, setUser }), [user]); \`\`\` ╔═══════════════════════════════════════════════════════════════════════════════╗ -║ 🚨 ZUSTAND STORE SELECTORS - #1 CRASH CAUSE - READ THIS 🚨 ║ +║ 🔥🔥🔥 ZUSTAND ABSOLUTE RULE - VIOLATION = INSTANT CRASH 🔥🔥🔥 ║ +║ ║ +║ ⚠️ ONLY RULE: Select individual primitives. NO EXCEPTIONS. ⚠️ ║ +║ ║ +║ ❌ BANNED FOREVER: useStore(s => ({ ... })) ║ +║ ❌ BANNED FOREVER: useStore() (no selector) ║ +║ ❌ BANNED FOREVER: useStore(s => s.getXxx()) (method calls) ║ +║ ║ +║ ✅ ONLY ALLOWED: useStore(s => s.primitiveValue) ║ ║ ║ ║ Zustand is SUBSCRIPTION-BASED, not context-based like React Context. ║ ║ Object/array selectors create NEW references every render = CRASH ║ ╚═══════════════════════════════════════════════════════════════════════════════╝ -❌ FORBIDDEN PATTERNS (ALL CAUSE INFINITE LOOPS): +❌ FORBIDDEN PATTERNS (THESE CAUSE INFINITE LOOPS): \`\`\`tsx -// Pattern 1: Object literal selector (with or without useShallow) +// 🔍 SCAN FOR: "useStore(s => ({" or "useStore((s) => ({" const { a, b, c } = useStore(s => ({ a: s.a, b: s.b, c: s.c })); // ❌ CRASH -// Pattern 1b: useShallow DOES NOT FIX object literal selectors! +// 🔍 SCAN FOR: "useStore(useShallow" import { useShallow } from 'zustand/react/shallow'; -const { a, b, c } = useStore(useShallow(s => ({ a: s.a, b: s.b, c: s.c }))); // ❌ STILL CRASHES! +const { a, b, c } = useStore(useShallow(s => ({ a: s.a, b: s.b, c: s.c }))); // ❌ CRASH // Why? You're creating a NEW object ({ a, b, c }) every render in the selector // useShallow can't help - the object reference is new every time -// Pattern 2: No selector (returns whole state object) +// 🔍 SCAN FOR: "useStore()" or "= useStore();" const { a, b, c } = useStore(); // ❌ CRASH const state = useStore(); // ❌ CRASH -// Pattern 3: Calling store methods (return new arrays/objects) +// 🔍 SCAN FOR: "useStore(s => s.get" or "useStore((state) => state.get" const items = useStore(s => s.getItems()); // ❌ INFINITE LOOP const filtered = useStore(s => s.items.filter(...)); // ❌ INFINITE LOOP const mapped = useStore(s => s.data.map(...)); // ❌ INFINITE LOOP \`\`\` ⚠️ CRITICAL MISCONCEPTION - READ THIS: -Many developers see "object literal selector without useShallow" and think "with useShallow" fixes it. -NO! useShallow is ONLY for objects that ALREADY EXIST in your store, not for creating new objects. +Many developers think useShallow fixes object-literal selectors. It does not. +Avoid using useShallow in selectors entirely. -✅ CORRECT PATTERNS (CHOOSE ONE): +✅ CORRECT PATTERN - ONLY ONE OPTION: \`\`\`tsx -// Option 1: Separate primitive selectors (RECOMMENDED - MOST EFFICIENT) +// ONLY ALLOWED: Separate primitive selectors const a = useStore(s => s.a); const b = useStore(s => s.b); const c = useStore(s => s.c); // ⚡ EFFICIENCY: Each selector ONLY triggers re-render when ITS value changes -// This is NOT inefficient! It's the BEST pattern for Zustand. - -// Option 2: useShallow for objects ALREADY IN the store (RARE - advanced) -import { useShallow } from 'zustand/react/shallow'; -const viewport = useStore(useShallow(s => s.viewport)); -// ✅ ONLY when 'viewport' is an object that EXISTS in your store: -// const store = create((set) => ({ viewport: { x: 0, y: 0 }, ... })) -// ❌ NOT for creating new objects: useStore(useShallow(s => ({ x: s.x, y: s.y }))) +// This is NOT inefficient! It's the BEST pattern for Zustand. This is actually good quality, elegant code! +// THERE IS NO OPTION 2. Only individual primitive selectors are allowed. +// If you need multiple values, call useStore multiple times - it's the ONLY correct pattern. -// Option 3: Store methods → Select primitives + useMemo in component +// For derived/computed values: Select primitives + useMemo in component const items = useStore(s => s.items); const filter = useStore(s => s.filter); const filtered = useMemo(() => @@ -662,7 +665,7 @@ const { user, isLoading } = useStore(); // ❌ CRASH - NOT THE SAME! - "The result of getSnapshot should be cached" - "Too many re-renders" -→ SCAN FOR: \`useStore(s => ({ ... }))\`, \`useStore(s => s.getXxx())\`, \`useStore()\` +→ SCAN FOR: \`useStore(s => ({\`, \`useStore(s => s.get\`, \`useStore()\` → FIX: Select ONLY primitives, compute derived values with useMemo ╔═══════════════════════════════════════════════════════════════════════════════╗ @@ -708,8 +711,8 @@ const handleClick = useCallback(() => setCount(prev => prev + 1), []); ✅ **NEVER call methods in selectors** - \`useStore(s => s.getXxx())\` = CRASH ✅ **No selector = CRASH** - \`useStore()\` returns whole object = infinite loop ✅ **Lift state from recursion** - Never useState inside recursive components -✅ **Actions are stable** - Zustand actions NOT in dependency arrays -✅ **Functional updates** - \`setState(prev => prev + 1)\` for correctness +✅ **Store actions are stable** - Zustand actions NOT in dependency arrays +✅ **Use functional updates** - \`setState(prev => prev + 1)\` for correctness ✅ **useRef for non-UI data** - Doesn't trigger re-renders ✅ **Derive, don't mirror** - \`const upper = prop.toUpperCase()\` not useState ✅ **DOM listeners stable** - Keep effect deps static; read live store values via refs; do not reattach listeners on every state change @@ -730,7 +733,7 @@ COMMON_PITFALLS: ` 3. **NO RUNTIME ERRORS:** Write robust, fault-tolerant code. Handle all edge cases gracefully with fallbacks. Never throw uncaught errors that can crash the application. 4. **NO UNDEFINED VALUES/PROPERTIES/FUNCTIONS/COMPONENTS etc:** Ensure all variables, functions, and components are defined before use. Never use undefined values. If you use something that isn't already defined, you need to define it. 5. **STATE UPDATE INTEGRITY:** Never call state setters directly during the render phase; all state updates must originate from event handlers or useEffect hooks to prevent infinite loops. - 6. **STATE SELECTOR STABILITY:** When using Zustand, ALWAYS select primitive values individually. NEVER \`useStore((state) => ({ ... }))\` (returns new object = infinite loop). NEVER \`useStore(s => s.getXxx())\` (method calls return new references). NEVER \`useStore()\` without selector (whole object = crash). See REACT INFINITE LOOP PREVENTION section for complete patterns. + 6. **🔥 ZUSTAND ZERO-TOLERANCE RULE 🔥:** ABSOLUTE LAW: useStore(s => s.primitive) ONLY. No object selectors. No exceptions. Any useStore(s => ({...})), useStore(), or useStore(s => s.getXxx()) = INSTANT CRASH. Multiple values? Call useStore multiple times - this is the ONLY correct pattern. See REACT INFINITE LOOP PREVENTION section for complete patterns. **UI/UX EXCELLENCE CRITICAL RULES:** 7. **VISUAL HIERARCHY CLARITY:** Every interface must have clear visual hierarchy - never create pages with uniform text sizes or equal visual weight for all elements From bb0d97e3236bf048f2bb5aa545a1b50fae86e81e Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 23 Oct 2025 15:13:08 -0400 Subject: [PATCH 059/150] fix: log results object correctly and suppress init error propagation in generator agent --- worker/agents/assistants/projectsetup.ts | 2 +- worker/agents/core/simpleGeneratorAgent.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/worker/agents/assistants/projectsetup.ts b/worker/agents/assistants/projectsetup.ts index f261b3d1..258d8167 100644 --- a/worker/agents/assistants/projectsetup.ts +++ b/worker/agents/assistants/projectsetup.ts @@ -127,7 +127,7 @@ ${error}`); context: this.inferenceContext, modelName: error? AIModels.GEMINI_2_5_FLASH : undefined, }); - this.logger.info(`Generated setup commands: ${results}`); + this.logger.info(`Generated setup commands: ${results.string}`); this.save([createAssistantMessage(results.string)]); return { commands: extractCommands(results.string) }; diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index 90915f28..4db6f01a 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -250,7 +250,7 @@ export class SimpleCodeGeneratorAgent extends Agent { this.logger().info("Initial commands executed successfully"); } catch (error) { this.logger().error("Error during async initialization:", error); - throw error; + // throw error; } } From 701fe8171b7098f0d470f8a56a9ae50b394f924f Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 23 Oct 2025 15:13:18 -0400 Subject: [PATCH 060/150] fix: only generate AI proxy vars when JWT secret is configured --- .../implementations/DeploymentManager.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/worker/agents/services/implementations/DeploymentManager.ts b/worker/agents/services/implementations/DeploymentManager.ts index 7ebe4432..26e130fb 100644 --- a/worker/agents/services/implementations/DeploymentManager.ts +++ b/worker/agents/services/implementations/DeploymentManager.ts @@ -533,14 +533,17 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa // Add AI proxy vars if AI template let localEnvVars: Record = {}; if (state.templateName?.includes('agents')) { - localEnvVars = { - "CF_AI_BASE_URL": generateAppProxyUrl(this.env), - "CF_AI_API_KEY": await generateAppProxyToken( - state.inferenceContext.agentId, - state.inferenceContext.userId, - this.env - ) - }; + const secret = this.env.AI_PROXY_JWT_SECRET; + if (typeof secret === 'string' && secret.trim().length > 0) { + localEnvVars = { + "CF_AI_BASE_URL": generateAppProxyUrl(this.env), + "CF_AI_API_KEY": await generateAppProxyToken( + state.inferenceContext.agentId, + state.inferenceContext.userId, + this.env + ) + }; + } } // Create instance From fcdc075ddcd9777ce88036e2d2d8058d87d84b24 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 23 Oct 2025 15:13:25 -0400 Subject: [PATCH 061/150] chore: increase LLM rate limits to 800/hr and 2000/day --- worker/services/rate-limit/config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worker/services/rate-limit/config.ts b/worker/services/rate-limit/config.ts index 52cf3fcc..d677a8d5 100644 --- a/worker/services/rate-limit/config.ts +++ b/worker/services/rate-limit/config.ts @@ -81,9 +81,9 @@ export const DEFAULT_RATE_LIMIT_SETTINGS: RateLimitSettings = { llmCalls: { enabled: true, store: RateLimitStore.DURABLE_OBJECT, - limit: 400, + limit: 800, period: 60 * 60, // 1 hour - dailyLimit: 1000, + dailyLimit: 2000, excludeBYOKUsers: true, }, }; From 693e742812c7e5323c746483107669e2a923eb62 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 23 Oct 2025 15:15:10 -0400 Subject: [PATCH 062/150] chore: wrangler update to 4.45.0 --- bun.lock | 48 ++++++++++++++++++++++++++++++++++++++---------- package.json | 2 +- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/bun.lock b/bun.lock index a90388e1..05528671 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "name": "vibesdk", "dependencies": { "@cloudflare/containers": "^0.0.28", - "@cloudflare/sandbox": "^0.4.7", + "@cloudflare/sandbox": "0.4.7", "@noble/ciphers": "^1.3.0", "@octokit/rest": "^22.0.0", "@radix-ui/react-accordion": "^1.2.12", @@ -121,7 +121,7 @@ "vite-plugin-monaco-editor": "^1.1.0", "vite-plugin-node-polyfills": "^0.24.0", "vitest": "^3.2.4", - "wrangler": "4.42.0", + "wrangler": "^4.45.0", }, }, }, @@ -230,15 +230,15 @@ "@cloudflare/vitest-pool-workers": ["@cloudflare/vitest-pool-workers@0.8.71", "", { "dependencies": { "birpc": "0.2.14", "cjs-module-lexer": "^1.2.3", "devalue": "^5.3.2", "miniflare": "4.20250906.0", "semver": "^7.7.1", "wrangler": "4.35.0", "zod": "^3.22.3" }, "peerDependencies": { "@vitest/runner": "2.0.x - 3.2.x", "@vitest/snapshot": "2.0.x - 3.2.x", "vitest": "2.0.x - 3.2.x" } }, "sha512-keu2HCLQfRNwbmLBCDXJgCFpANTaYnQpE01fBOo4CNwiWHUT7SZGN7w64RKiSWRHyYppStXBuE5Ng7F42+flpg=="], - "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20251001.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-y1ST/cCscaRewWRnsHZdWbgiLJbki5UMGd0hMo/FLqjlztwPeDgQ5CGm5jMiCDdw/IBCpWxEukftPYR34rWNog=="], + "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20251011.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-0DirVP+Z82RtZLlK2B+VhLOkk+ShBqDYO/jhcRw4oVlp0TOvk3cOVZChrt3+y3NV8Y/PYgTEywzLKFSziK4wCg=="], - "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20251001.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+z4QHHZ/Yix82zLFYS+ZS2UV09IENFPwDCEKUWfnrM9Km2jOOW3Ua4hJNob1EgQUYs8fFZo7k5O/tpwxMsSbbQ=="], + "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20251011.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-1WuFBGwZd15p4xssGN/48OE2oqokIuc51YvHvyNivyV8IYnAs3G9bJNGWth1X7iMDPe4g44pZrKhRnISS2+5dA=="], - "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20251001.0", "", { "os": "linux", "cpu": "x64" }, "sha512-hGS+O2V9Mm2XjJUaB9ZHMA5asDUaDjKko42e+accbew0PQR7zrAl1afdII6hMqCLV4tk4GAjvhv281pN4g48rg=="], + "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20251011.0", "", { "os": "linux", "cpu": "x64" }, "sha512-BccMiBzFlWZyFghIw2szanmYJrJGBGHomw2y/GV6pYXChFzMGZkeCEMfmCyJj29xczZXxcZmUVJxNy4eJxO8QA=="], - "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20251001.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-QYaMK+pRgt28N7CX1JlJ+ToegJF9LxzqdT7MjWqPgVj9D2WTyIhBVYl3wYjJRcgOlnn+DRt42+li4T64CPEeuA=="], + "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20251011.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-79o/216lsbAbKEVDZYXR24ivEIE2ysDL9jvo0rDTkViLWju9dAp3CpyetglpJatbSi3uWBPKZBEOqN68zIjVsQ=="], - "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20251001.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ospnDR/FlyRvrv9DSHuxDAXmzEBLDUiAHQrQHda1iUH9HqxnNQ8giz9VlPfq7NIRc7bQ1ZdIYPGLJOY4Q366Ng=="], + "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20251011.0", "", { "os": "win32", "cpu": "x64" }, "sha512-RIXUQRchFdqEvaUqn1cXZXSKjpqMaSaVAkI5jNZ8XzAw/bw2bcdOVUtakrflgxDprltjFb0PTNtuss1FKtH9Jg=="], "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250911.0", "", {}, "sha512-Ardq4aDdNOfnvU/qC8anznZYSsMlfZnMgLAdwxESf3bMdgkb+GV01LpY8NzridFe7cFeprfiDNANBZoeUeEDlg=="], @@ -2542,9 +2542,9 @@ "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], - "workerd": ["workerd@1.20251001.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251001.0", "@cloudflare/workerd-darwin-arm64": "1.20251001.0", "@cloudflare/workerd-linux-64": "1.20251001.0", "@cloudflare/workerd-linux-arm64": "1.20251001.0", "@cloudflare/workerd-windows-64": "1.20251001.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-oT/K4YWNhmwpVmGeaHNmF7mLRfgjszlVr7lJtpS4jx5khmxmMzWZEEQRrJEpgzeHP6DOq9qWLPNT0bjMK7TchQ=="], + "workerd": ["workerd@1.20251011.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251011.0", "@cloudflare/workerd-darwin-arm64": "1.20251011.0", "@cloudflare/workerd-linux-64": "1.20251011.0", "@cloudflare/workerd-linux-arm64": "1.20251011.0", "@cloudflare/workerd-windows-64": "1.20251011.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-Dq35TLPEJAw7BuYQMkN3p9rge34zWMU2Gnd4DSJFeVqld4+DAO2aPG7+We2dNIAyM97S8Y9BmHulbQ00E0HC7Q=="], - "wrangler": ["wrangler@4.42.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.6", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251001.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.21", "workerd": "1.20251001.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251001.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-OZXiUSfGD66OVkncDbjZtqrsH6bWPRQMYc6RmMbkzYm/lEvJ8lvARKcqDgEyq8zDAgJAivlMQLyPtKQoVjQ/4g=="], + "wrangler": ["wrangler@4.45.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.8", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251011.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.21", "workerd": "1.20251011.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251011.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-2qM6bHw8l7r89Z9Y5A7Wn4L9U+dFoLjYgEUVpqy7CcmXpppL3QIYqU6rU5lre7/SRzBuPu/H93Vwfh538gZ3iw=="], "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], @@ -2812,6 +2812,8 @@ "miller-rabin/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], + "miniflare/workerd": ["workerd@1.20251001.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251001.0", "@cloudflare/workerd-darwin-arm64": "1.20251001.0", "@cloudflare/workerd-linux-64": "1.20251001.0", "@cloudflare/workerd-linux-arm64": "1.20251001.0", "@cloudflare/workerd-windows-64": "1.20251001.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-oT/K4YWNhmwpVmGeaHNmF7mLRfgjszlVr7lJtpS4jx5khmxmMzWZEEQRrJEpgzeHP6DOq9qWLPNT0bjMK7TchQ=="], + "miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], "node-stdlib-browser/pkg-dir": ["pkg-dir@5.0.0", "", { "dependencies": { "find-up": "^5.0.0" } }, "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA=="], @@ -3218,10 +3220,12 @@ "uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "wrangler/@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.7.6", "", { "peerDependencies": { "unenv": "2.0.0-rc.21", "workerd": "^1.20250927.0" }, "optionalPeers": ["workerd"] }, "sha512-ykG2nd3trk6jbknRCH69xL3RpGLLbKCrbTbWSOvKEq7s4jH06yLrQlRr/q9IU+dK9p1JY1EXqhFK7VG5KqhzmQ=="], + "wrangler/@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.7.8", "", { "peerDependencies": { "unenv": "2.0.0-rc.21", "workerd": "^1.20250927.0" }, "optionalPeers": ["workerd"] }, "sha512-Ky929MfHh+qPhwCapYrRPwPVHtA2Ioex/DbGZyskGyNRDe9Ru3WThYZivyNVaPy5ergQSgMs9OKrM9Ajtz9F6w=="], "wrangler/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], + "wrangler/miniflare": ["miniflare@4.20251011.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251011.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-Qbw1Z8HTYM1adWl6FAtzhrj34/6dPRDPwdYOx21dkae8a/EaxbMzRIPbb4HKVGMVvtqbK1FaRCgDLVLolNzGHg=="], + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], "wrap-ansi/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], @@ -3238,6 +3242,8 @@ "@cloudflare/vite-plugin/wrangler/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], + "@cloudflare/vite-plugin/wrangler/workerd": ["workerd@1.20251001.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251001.0", "@cloudflare/workerd-darwin-arm64": "1.20251001.0", "@cloudflare/workerd-linux-64": "1.20251001.0", "@cloudflare/workerd-linux-arm64": "1.20251001.0", "@cloudflare/workerd-windows-64": "1.20251001.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-oT/K4YWNhmwpVmGeaHNmF7mLRfgjszlVr7lJtpS4jx5khmxmMzWZEEQRrJEpgzeHP6DOq9qWLPNT0bjMK7TchQ=="], + "@cloudflare/vitest-pool-workers/miniflare/undici": ["undici@7.16.0", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="], "@cloudflare/vitest-pool-workers/miniflare/workerd": ["workerd@1.20250906.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250906.0", "@cloudflare/workerd-darwin-arm64": "1.20250906.0", "@cloudflare/workerd-linux-64": "1.20250906.0", "@cloudflare/workerd-linux-arm64": "1.20250906.0", "@cloudflare/workerd-windows-64": "1.20250906.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-ryVyEaqXPPsr/AxccRmYZZmDAkfQVjhfRqrNTlEeN8aftBk6Ca1u7/VqmfOayjCXrA+O547TauebU+J3IpvFXw=="], @@ -3406,6 +3412,16 @@ "jest-worker/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "miniflare/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20251001.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-y1ST/cCscaRewWRnsHZdWbgiLJbki5UMGd0hMo/FLqjlztwPeDgQ5CGm5jMiCDdw/IBCpWxEukftPYR34rWNog=="], + + "miniflare/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20251001.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+z4QHHZ/Yix82zLFYS+ZS2UV09IENFPwDCEKUWfnrM9Km2jOOW3Ua4hJNob1EgQUYs8fFZo7k5O/tpwxMsSbbQ=="], + + "miniflare/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20251001.0", "", { "os": "linux", "cpu": "x64" }, "sha512-hGS+O2V9Mm2XjJUaB9ZHMA5asDUaDjKko42e+accbew0PQR7zrAl1afdII6hMqCLV4tk4GAjvhv281pN4g48rg=="], + + "miniflare/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20251001.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-QYaMK+pRgt28N7CX1JlJ+ToegJF9LxzqdT7MjWqPgVj9D2WTyIhBVYl3wYjJRcgOlnn+DRt42+li4T64CPEeuA=="], + + "miniflare/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20251001.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ospnDR/FlyRvrv9DSHuxDAXmzEBLDUiAHQrQHda1iUH9HqxnNQ8giz9VlPfq7NIRc7bQ1ZdIYPGLJOY4Q366Ng=="], + "npm/are-we-there-yet/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "npm/bl/readable-stream": ["readable-stream@2.0.6", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "~1.0.0", "process-nextick-args": "~1.0.6", "string_decoder": "~0.10.x", "util-deprecate": "~1.0.1" } }, "sha512-TXcFfb63BQe1+ySzsHZI/5v1aJPCShfqvWJ64ayNImXMsN1Cd0YGk/wm8KB7/OeessgPc9QvS9Zou8QTkFzsLw=="], @@ -3544,6 +3560,8 @@ "wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], + "wrangler/miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], @@ -3600,6 +3618,16 @@ "@cloudflare/vite-plugin/wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], + "@cloudflare/vite-plugin/wrangler/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20251001.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-y1ST/cCscaRewWRnsHZdWbgiLJbki5UMGd0hMo/FLqjlztwPeDgQ5CGm5jMiCDdw/IBCpWxEukftPYR34rWNog=="], + + "@cloudflare/vite-plugin/wrangler/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20251001.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+z4QHHZ/Yix82zLFYS+ZS2UV09IENFPwDCEKUWfnrM9Km2jOOW3Ua4hJNob1EgQUYs8fFZo7k5O/tpwxMsSbbQ=="], + + "@cloudflare/vite-plugin/wrangler/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20251001.0", "", { "os": "linux", "cpu": "x64" }, "sha512-hGS+O2V9Mm2XjJUaB9ZHMA5asDUaDjKko42e+accbew0PQR7zrAl1afdII6hMqCLV4tk4GAjvhv281pN4g48rg=="], + + "@cloudflare/vite-plugin/wrangler/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20251001.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-QYaMK+pRgt28N7CX1JlJ+ToegJF9LxzqdT7MjWqPgVj9D2WTyIhBVYl3wYjJRcgOlnn+DRt42+li4T64CPEeuA=="], + + "@cloudflare/vite-plugin/wrangler/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20251001.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ospnDR/FlyRvrv9DSHuxDAXmzEBLDUiAHQrQHda1iUH9HqxnNQ8giz9VlPfq7NIRc7bQ1ZdIYPGLJOY4Q366Ng=="], + "@cloudflare/vitest-pool-workers/miniflare/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250906.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-E+X/YYH9BmX0ew2j/mAWFif2z05NMNuhCTlNYEGLkqMe99K15UewBqajL9pMcMUKxylnlrEoK3VNxl33DkbnPA=="], "@cloudflare/vitest-pool-workers/miniflare/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250906.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-X5apsZ1SFW4FYTM19ISHf8005FJMPfrcf4U5rO0tdj+TeJgQgXuZ57IG0WeW7SpLVeBo8hM6WC8CovZh41AfnA=="], diff --git a/package.json b/package.json index 342ed69d..0c04c021 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "vite-plugin-monaco-editor": "^1.1.0", "vite-plugin-node-polyfills": "^0.24.0", "vitest": "^3.2.4", - "wrangler": "4.42.0" + "wrangler": "4.45.0" }, "prettier": { "singleQuote": true, From f4f4f7ba2ac441612f6cbae7a20e3c54da882231 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 23 Oct 2025 15:39:23 -0400 Subject: [PATCH 063/150] perf: optimize file writing with batched shell script to reduce API requests --- worker/services/sandbox/sandboxSdkClient.ts | 131 ++++++++++++++++---- 1 file changed, 104 insertions(+), 27 deletions(-) diff --git a/worker/services/sandbox/sandboxSdkClient.ts b/worker/services/sandbox/sandboxSdkClient.ts index d0638222..ae9ca61d 100644 --- a/worker/services/sandbox/sandboxSdkClient.ts +++ b/worker/services/sandbox/sandboxSdkClient.ts @@ -248,18 +248,100 @@ export class SandboxSdkClient extends BaseSandboxService { } } - private async writeFile(targetPath: string, data: string, session?: ExecutionSession) { - if (!session) { - session = await this.getDefaultSession() - } + /** + * Write multiple files efficiently using a single shell script + * Reduces 2N requests to just 2 requests regardless of file count + * Uses base64 encoding to handle all content safely + */ + private async writeFilesViaScript( + files: Array<{path: string, content: string}>, + session: ExecutionSession + ): Promise> { + if (files.length === 0) return []; + + this.logger.info('Writing files via shell script', { fileCount: files.length }); + + // Generate shell script + const scriptLines = ['#!/bin/bash']; - // Ensure parent directory exists (mkdir -p is idempotent) - const dir = targetPath.substring(0, targetPath.lastIndexOf('/')); - if (dir) { - await session.exec(`mkdir -p "${dir}"`); + for (const { path, content } of files) { + const utf8Bytes = new TextEncoder().encode(content); + + // Convert bytes to base64 in chunks to avoid stack overflow + const chunkSize = 8192; + const base64Chunks: string[] = []; + + for (let i = 0; i < utf8Bytes.length; i += chunkSize) { + const chunk = utf8Bytes.slice(i, i + chunkSize); + // Convert chunk to binary string + let binaryString = ''; + for (let j = 0; j < chunk.length; j++) { + binaryString += String.fromCharCode(chunk[j]); + } + // Encode chunk to base64 + base64Chunks.push(btoa(binaryString)); + } + + const base64 = base64Chunks.join(''); + + scriptLines.push( + `mkdir -p "$(dirname "${path}")" && echo '${base64}' | base64 -d > "${path}" && echo "OK:${path}" || echo "FAIL:${path}"` + ); + } + + const script = scriptLines.join('\n'); + const scriptPath = '/tmp/batch_write.sh'; + + try { + // Write script (1 request) + const writeResult = await session.writeFile(scriptPath, script); + if (!writeResult.success) { + throw new Error('Failed to write batch script'); + } + + // Execute with bash + const { stdout, stderr } = await session.exec(`bash ${scriptPath}`, { timeout: 60000 }); + + // Parse results from output + const output = stdout + stderr; + const matches = output.matchAll(/OK:(.+)/g); + const successPaths = new Set(); + for (const match of matches) { + if (match[1]) successPaths.add(match[1]); + } + + const results = files.map(({ path }) => ({ + file: path, + success: successPaths.has(path), + error: successPaths.has(path) ? undefined : 'Write failed' + })); + + const successCount = successPaths.size; + const failedCount = files.length - successCount; + + if (failedCount > 0) { + this.logger.warn('Batch write completed with errors', { + total: files.length, + success: successCount, + failed: failedCount + }); + } else { + this.logger.info('Batch write completed', { + total: files.length, + success: successCount + }); + } + + return results; + + } catch (error) { + this.logger.error('Batch write failed', error); + return files.map(({ path }) => ({ + file: path, + success: false, + error: error instanceof Error ? error.message : 'Unknown error' + })); } - - return await session.writeFile(targetPath, data); } async updateProjectName(instanceId: string, projectName: string): Promise { @@ -1202,26 +1284,21 @@ export class SandboxSdkClient extends BaseSandboxService { const filteredFiles = files.filter(file => !donttouchFiles.has(file.filePath)); - const writePromises = filteredFiles.map(file => this.writeFile(`/workspace/${instanceId}/${file.filePath}`, file.fileContents, session)); + // Use batch script for efficient writing (3 requests for any number of files) + const filesToWrite = filteredFiles.map(file => ({ + path: `/workspace/${instanceId}/${file.filePath}`, + content: file.fileContents + })); - const writeResults = await Promise.all(writePromises); + const writeResults = await this.writeFilesViaScript(filesToWrite, session); + // Map results back to original format for (const writeResult of writeResults) { - if (writeResult.success) { - results.push({ - file: writeResult.path, - success: true - }); - - this.logger.info('File written', { filePath: writeResult.path }); - } else { - this.logger.error('File write failed', { filePath: writeResult.path }); - results.push({ - file: writeResult.path, - success: false, - error: 'Unknown error' - }); - } + results.push({ + file: writeResult.file.replace(`/workspace/${instanceId}/`, ''), + success: writeResult.success, + error: writeResult.error + }); } // Add files that were not written to results From ee61d464fbcd712a20ff822bdb76e0cf881e1aa3 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 23 Oct 2025 20:37:53 -0400 Subject: [PATCH 064/150] fix: concurrent deploy + use nanoid + stalestate --- worker/agents/core/simpleGeneratorAgent.ts | 82 ++++--- .../implementations/DeploymentManager.ts | 207 +++++++++--------- 2 files changed, 148 insertions(+), 141 deletions(-) diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index 4db6f01a..d791ce03 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -48,6 +48,7 @@ import { ConversationMessage, ConversationState } from '../inferutils/common'; import { DeepCodeDebugger } from '../assistants/codeDebugger'; import { DeepDebugResult } from './types'; import { StateMigration } from './stateMigration'; +import { generateNanoId } from 'worker/utils/idGenerator'; interface Operations { codeReview: CodeReviewOperation; @@ -187,7 +188,7 @@ export class SimpleCodeGeneratorAgent extends Agent { ): Promise { const { query, language, frameworks, hostname, inferenceContext, templateInfo } = initArgs; - const sandboxSessionId = inferenceContext.agentId; // Let the initial sessionId be the agentId + const sandboxSessionId = generateNanoId(); this.initLogger(inferenceContext.agentId, sandboxSessionId, inferenceContext.userId); // Generate a blueprint @@ -501,6 +502,35 @@ export class SimpleCodeGeneratorAgent extends Agent { }; } + private createNewIncompletePhase(phaseConcept: PhaseConceptType) { + this.setState({ + ...this.state, + generatedPhases: [...this.state.generatedPhases, { + ...phaseConcept, + completed: false + }] + }) + + this.logger().info("Created new incomplete phase:", JSON.stringify(this.state.generatedPhases, null, 2)); + } + + private markPhaseComplete(phaseName: string) { + // First find the phase + const phases = this.state.generatedPhases; + if (!phases.some(p => p.name === phaseName)) { + this.logger().error(`Phase ${phaseName} not found in generatedPhases array`); + throw new Error(`Phase ${phaseName} not found in generatedPhases array`); + } + + // Update the phase + this.setState({ + ...this.state, + generatedPhases: phases.map(p => p.name === phaseName ? { ...p, completed: true } : p) + }); + + this.logger().info("Completed phases:", JSON.stringify(phases, null, 2)); + } + private broadcastError(context: string, error: unknown): void { const errorMessage = error instanceof Error ? error.message : String(error); this.logger().error(`${context}:`, error); @@ -581,21 +611,29 @@ export class SimpleCodeGeneratorAgent extends Agent { message: 'Starting code generation', totalFiles: this.getTotalFiles() }); + this.logger().info('Starting code generation', { + totalFiles: this.getTotalFiles() + }); let currentDevState = CurrentDevState.PHASE_IMPLEMENTING; const generatedPhases = this.state.generatedPhases; const incompletedPhases = generatedPhases.filter(phase => !phase.completed); let phaseConcept : PhaseConceptType | undefined; if (incompletedPhases.length > 0) { phaseConcept = incompletedPhases[incompletedPhases.length - 1]; + this.logger().info('Resuming code generation from incompleted phase', { + phase: phaseConcept + }); } else if (generatedPhases.length > 0) { currentDevState = CurrentDevState.PHASE_GENERATING; + this.logger().info('Resuming code generation after generating all phases', { + phase: generatedPhases[generatedPhases.length - 1] + }); } else { phaseConcept = this.state.blueprint.initialPhase; - this.setState({ - ...this.state, - currentPhase: phaseConcept, - generatedPhases: [{...phaseConcept, completed: false}] + this.logger().info('Starting code generation from initial phase', { + phase: phaseConcept }); + this.createNewIncompletePhase(phaseConcept); } let staticAnalysisCache: StaticAnalysisResponse | undefined; @@ -902,16 +940,7 @@ export class SimpleCodeGeneratorAgent extends Agent { lastPhase: true } - this.setState({ - ...this.state, - generatedPhases: [ - ...this.state.generatedPhases, - { - ...phaseConcept, - completed: false - } - ] - }); + this.createNewIncompletePhase(phaseConcept); const currentIssues = await this.fetchAllIssues(true); @@ -1030,16 +1059,7 @@ export class SimpleCodeGeneratorAgent extends Agent { return undefined; } - this.setState({ - ...this.state, - generatedPhases: [ - ...this.state.generatedPhases, - { - ...result, - completed: false - } - ], - }); + this.createNewIncompletePhase(result); // Notify phase generation complete this.broadcast(WebSocketMessageResponses.PHASE_GENERATED, { message: `Generated next phase: ${result.name}`, @@ -1106,7 +1126,7 @@ export class SimpleCodeGeneratorAgent extends Agent { message: `Validating files for phase: ${phase.name}`, phase: phase, }); - + // Await the already-created realtime code fixer promises const finalFiles = await Promise.allSettled(result.fixedFilePromises).then((results: PromiseSettledResult[]) => { return results.map((result) => { @@ -1164,15 +1184,7 @@ export class SimpleCodeGeneratorAgent extends Agent { message: "Files generated successfully for phase" }); - const previousPhases = this.state.generatedPhases; - // Replace the phase with the new one - const updatedPhases = previousPhases.map(p => p.name === phase.name ? {...p, completed: true} : p); - this.setState({ - ...this.state, - generatedPhases: updatedPhases, - }); - - this.logger().info("Completed phases:", JSON.stringify(updatedPhases, null, 2)); + this.markPhaseComplete(phase.name); return { files: finalFiles, diff --git a/worker/agents/services/implementations/DeploymentManager.ts b/worker/agents/services/implementations/DeploymentManager.ts index 26e130fb..f91d8f8d 100644 --- a/worker/agents/services/implementations/DeploymentManager.ts +++ b/worker/agents/services/implementations/DeploymentManager.ts @@ -8,15 +8,15 @@ import { import { BootstrapResponse, GitHubPushRequest, StaticAnalysisResponse, RuntimeError, PreviewType } from '../../../services/sandbox/sandboxTypes'; import { GitHubExportResult } from '../../../services/github/types'; import { FileOutputType } from '../../schemas'; -import { generateId } from '../../../utils/idGenerator'; +import { generateId, generateNanoId } from '../../../utils/idGenerator'; import { generateAppProxyToken, generateAppProxyUrl } from '../../../services/aigateway-proxy/controller'; import { BaseAgentService } from './BaseAgentService'; import { ServiceOptions } from '../interfaces/IServiceOptions'; import { BaseSandboxService } from '../../../services/sandbox/BaseSandboxService'; import { getSandboxService } from '../../../services/sandbox/factory'; -const MAX_DEPLOYMENT_RETRIES = 3; -const DEPLOYMENT_TIMEOUT_MS = 60000; +const PER_ATTEMPT_TIMEOUT_MS = 60000; // 60 seconds per individual attempt +const MASTER_DEPLOYMENT_TIMEOUT_MS = 300000; // 5 minutes total const HEALTH_CHECK_INTERVAL_MS = 30000; /** @@ -94,7 +94,7 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa } private generateNewSessionId(): string { - return generateId(); + return generateNanoId(); } /** @@ -260,6 +260,8 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa /** * Main deployment method * Callbacks allow agent to broadcast at the right times + * All concurrent callers share the same promise and wait together + * Retries indefinitely until success or master timeout (5 minutes) */ async deployToSandbox( files: FileOutputType[] = [], @@ -270,18 +272,14 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa ): Promise { const logger = this.getLog(); - // Queue management - prevent concurrent deployments + // All concurrent callers wait on the same promise if (this.currentDeploymentPromise) { logger.info('Deployment already in progress, waiting for completion'); - try { - const result = await this.currentDeploymentPromise; - if (result) { - logger.info('Previous deployment completed successfully, returning its result'); - return result; - } - } catch (error) { - logger.warn('Previous deployment failed, proceeding with new deployment:', error); - } + return await this.withTimeout( + this.currentDeploymentPromise, + MASTER_DEPLOYMENT_TIMEOUT_MS, + 'Deployment failed after 5 minutes' + ).catch(() => null); // Convert timeout to null like first caller } logger.info("Deploying to sandbox", { files: files.length, redeploy, commitMessage, sessionId: this.getSessionId() }); @@ -292,130 +290,127 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa redeploy, commitMessage, clearLogs, - MAX_DEPLOYMENT_RETRIES, callbacks ); try { - // Wrap with timeout + // Master timeout: 5 minutes total + // This doesn't break the underlying operation - it just stops waiting const result = await this.withTimeout( this.currentDeploymentPromise, - DEPLOYMENT_TIMEOUT_MS, - 'Deployment timed out', - () => { - logger.warn('Deployment timed out, resetting sessionId to provision new sandbox instance'); - this.resetSessionId(); - } + MASTER_DEPLOYMENT_TIMEOUT_MS, + 'Deployment failed after 5 minutes of retries' + // No onTimeout callback - don't break the operation ); return result; + } catch (error) { + // Master timeout reached - all retries exhausted + logger.error('Deployment permanently failed after master timeout:', error); + return null; } finally { this.currentDeploymentPromise = null; } } /** - * Execute deployment with retry logic - * Handles error-specific sessionId reset and exponential backoff + * Execute deployment with infinite retry until success + * Each attempt has its own timeout + * Resets sessionId after consecutive failures */ private async executeDeploymentWithRetry( files: FileOutputType[], redeploy: boolean, commitMessage: string | undefined, clearLogs: boolean, - retries: number, callbacks?: SandboxDeploymentCallbacks - ): Promise { + ): Promise { const logger = this.getLog(); + let attempt = 0; + const maxAttemptsBeforeSessionReset = 3; + + while (true) { + attempt++; + logger.info(`Deployment attempt ${attempt}`, { sessionId: this.getSessionId() }); + + try { + // Callback: deployment starting (only on first attempt) + callbacks?.onStarted?.({ + message: "Deploying code to sandbox service", + files: files.map(f => ({ filePath: f.filePath })) + }); - try { - // Callback: deployment actually starting now - callbacks?.onStarted?.({ - message: "Deploying code to sandbox service", - files: files.map(f => ({ filePath: f.filePath })) - }); - - logger.info('Deploying code to sandbox service'); - - // Core deployment - const result = await this.deploy({ - files, - redeploy, - commitMessage, - clearLogs - }); - - // Start health check after successful deployment - if (result.redeployed || this.healthCheckInterval === null) { - this.startHealthCheckInterval(result.sandboxInstanceId); - } - - const preview = { - runId: result.sandboxInstanceId, - previewURL: result.previewURL, - tunnelURL: result.tunnelURL - }; - - // Callback: deployment completed - callbacks?.onCompleted?.({ - message: "Deployment completed", - instanceId: preview.runId, - previewURL: preview.previewURL ?? '', - tunnelURL: preview.tunnelURL ?? '' - }); - - return preview; - } catch (error) { - logger.error("Error deploying to sandbox service:", error, { - sessionId: this.getSessionId(), - sandboxInstanceId: this.getState().sandboxInstanceId - }); + // Core deployment with per-attempt timeout + const deployPromise = this.deploy({ + files, + redeploy, + commitMessage, + clearLogs + }); + + const result = await this.withTimeout( + deployPromise, + PER_ATTEMPT_TIMEOUT_MS, + `Deployment attempt ${attempt} timed out` + // No onTimeout callback - don't break anything + ); - const errorMsg = error instanceof Error ? error.message : String(error); + // Success! Start health check and return + if (result.redeployed || this.healthCheckInterval === null) { + this.startHealthCheckInterval(result.sandboxInstanceId); + } - // Handle specific errors that require session reset - if (errorMsg.includes('Network connection lost') || - errorMsg.includes('Container service disconnected') || - errorMsg.includes('Internal error in Durable Object storage')) { - logger.warn('Session-level error detected, resetting sessionId'); - this.resetSessionId(); - } + const preview = { + runId: result.sandboxInstanceId, + previewURL: result.previewURL, + tunnelURL: result.tunnelURL + }; - // Clear instance ID from state - const state = this.getState(); - this.setState({ - ...state, - sandboxInstanceId: undefined - }); + callbacks?.onCompleted?.({ + message: "Deployment completed", + instanceId: preview.runId, + previewURL: preview.previewURL ?? '', + tunnelURL: preview.tunnelURL ?? '' + }); - // Retry logic with exponential backoff - if (retries > 0) { - logger.info(`Retrying deployment, ${retries} attempts remaining`); + logger.info('Deployment succeeded', { attempt, sessionId: this.getSessionId() }); + return preview; + + } catch (error) { + logger.warn(`Deployment attempt ${attempt} failed:`, error); - // Exponential backoff - const attempt = MAX_DEPLOYMENT_RETRIES - retries + 1; - const delayMs = Math.pow(2, attempt - 1) * 1000; - await new Promise(resolve => setTimeout(resolve, delayMs)); + const errorMsg = error instanceof Error ? error.message : String(error); - return this.executeDeploymentWithRetry( - files, - redeploy, - commitMessage, - clearLogs, - retries - 1, - callbacks - ); + // Handle specific errors that require session reset + if (errorMsg.includes('Network connection lost') || + errorMsg.includes('Container service disconnected') || + errorMsg.includes('Internal error in Durable Object storage')) { + logger.warn('Session-level error detected, resetting sessionId'); + this.resetSessionId(); + } + + // After consecutive failures, reset session to get fresh sandbox + if (attempt % maxAttemptsBeforeSessionReset === 0) { + logger.warn(`${attempt} consecutive failures, resetting sessionId for fresh sandbox`); + this.resetSessionId(); + } + + // Clear instance ID from state + this.setState({ + ...this.getState(), + sandboxInstanceId: undefined + }); + + // Exponential backoff before retry (capped at 30 seconds) + const backoffMs = Math.min(1000 * Math.pow(2, Math.min(attempt - 1, 5)), 30000); + logger.info(`Retrying deployment in ${backoffMs}ms...`); + await new Promise(resolve => setTimeout(resolve, backoffMs)); + + // Loop continues - retry indefinitely until master timeout } - - // Callback: deployment failed after all retries - logger.error('Deployment failed after all retries'); - callbacks?.onError?.({ - error: `Error deploying to sandbox service: ${errorMsg}. Please report an issue if this persists` - }); - - return null; } } + /** * Deploy files to sandbox instance (core deployment) */ @@ -499,7 +494,7 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa // Update state with new instance ID this.setState({ - ...state, + ...this.getState(), sandboxInstanceId: results.runId, }); From 241ffd61944a5a73a5b32cb5be9cd0716ae41c00 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 23 Oct 2025 20:38:05 -0400 Subject: [PATCH 065/150] chore: remove request logging in worker fetch handler --- worker/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker/index.ts b/worker/index.ts index cc9546da..9fea8b63 100644 --- a/worker/index.ts +++ b/worker/index.ts @@ -110,7 +110,7 @@ async function handleUserAppRequest(request: Request, env: Env): Promise { - logger.info(`Received request: ${request.method} ${request.url}`); + // logger.info(`Received request: ${request.method} ${request.url}`); // --- Pre-flight Checks --- // 1. Critical configuration check: Ensure custom domain is set. From 596cc90fd75cc435083a1aedc116f127a70a8c72 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 23 Oct 2025 20:38:21 -0400 Subject: [PATCH 066/150] refactor: move preview deployment request inside files check condition --- src/routes/chat/utils/handle-websocket-message.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/routes/chat/utils/handle-websocket-message.ts b/src/routes/chat/utils/handle-websocket-message.ts index 413bf0f9..f07f53a9 100644 --- a/src/routes/chat/utils/handle-websocket-message.ts +++ b/src/routes/chat/utils/handle-websocket-message.ts @@ -244,15 +244,13 @@ export function createWebSocketMessageHandler(deps: HandleMessageDeps) { if (state.generatedFilesMap && Object.keys(state.generatedFilesMap).length > 0) { updateStage('code', { status: 'completed' }); updateStage('validate', { status: 'completed' }); + if (urlChatId !== 'new') { + logger.debug('🚀 Requesting preview deployment for existing chat with files'); + sendWebSocketMessage(websocket, 'preview'); + } } setIsInitialStateRestored(true); - - if (state.generatedFilesMap && Object.keys(state.generatedFilesMap).length > 0 && - urlChatId !== 'new') { - logger.debug('🚀 Requesting preview deployment for existing chat with files'); - sendWebSocketMessage(websocket, 'preview'); - } } break; } From 97a3c16af34a873342cf80c809f4123348ae17af Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 23 Oct 2025 20:38:34 -0400 Subject: [PATCH 067/150] refactor: simplify state management in FileManager by removing redundant state variable --- worker/agents/services/implementations/FileManager.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/worker/agents/services/implementations/FileManager.ts b/worker/agents/services/implementations/FileManager.ts index ef023842..a1d920d1 100644 --- a/worker/agents/services/implementations/FileManager.ts +++ b/worker/agents/services/implementations/FileManager.ts @@ -41,8 +41,7 @@ export class FileManager implements IFileManager { } saveGeneratedFiles(files: FileOutputType[]): FileState[] { - const state = this.stateManager.getState(); - const filesMap = { ...state.generatedFilesMap }; + const filesMap = { ...this.stateManager.getState().generatedFilesMap }; const fileStates: FileState[] = []; for (const file of files) { @@ -78,22 +77,21 @@ export class FileManager implements IFileManager { } this.stateManager.setState({ - ...state, + ...this.stateManager.getState(), generatedFilesMap: filesMap }); return fileStates; } deleteFiles(filePaths: string[]): void { - const state = this.stateManager.getState(); - const newFilesMap = { ...state.generatedFilesMap }; + const newFilesMap = { ...this.stateManager.getState().generatedFilesMap }; for (const filePath of filePaths) { delete newFilesMap[filePath]; } this.stateManager.setState({ - ...state, + ...this.stateManager.getState(), generatedFilesMap: newFilesMap }); } From 68678ac3a06b71d936a8b5f8e9e146b093d6d9a0 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Thu, 23 Oct 2025 20:38:43 -0400 Subject: [PATCH 068/150] feat: enhance template file filtering with pattern matching and add nanoid generator --- worker/services/sandbox/utils.ts | 16 ++++++++++++---- worker/utils/idGenerator.ts | 6 ++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/worker/services/sandbox/utils.ts b/worker/services/sandbox/utils.ts index 4ff4e248..865583c0 100644 --- a/worker/services/sandbox/utils.ts +++ b/worker/services/sandbox/utils.ts @@ -1,10 +1,18 @@ import { TemplateDetails, TemplateFile } from "./sandboxTypes"; export function getTemplateImportantFiles(templateDetails: TemplateDetails, filterRedacted: boolean = true): TemplateFile[] { - return templateDetails.importantFiles.map(filePath => ({ - filePath, - fileContents: filterRedacted && templateDetails.redactedFiles.includes(filePath) ? 'REDACTED' : templateDetails.allFiles[filePath] - })).filter(f => f.fileContents); + const { importantFiles, allFiles, redactedFiles } = templateDetails; + const redactedSet = new Set(redactedFiles); + + const result: TemplateFile[] = []; + for (const [filePath, fileContents] of Object.entries(allFiles)) { + if (importantFiles.some(pattern => filePath === pattern || filePath.startsWith(pattern))) { + const contents = filterRedacted && redactedSet.has(filePath) ? 'REDACTED' : fileContents; + if (contents) result.push({ filePath, fileContents: contents }); + } + } + + return result; } export function getTemplateFiles(templateDetails: TemplateDetails): TemplateFile[] { diff --git a/worker/utils/idGenerator.ts b/worker/utils/idGenerator.ts index 44ca1508..cb102195 100644 --- a/worker/utils/idGenerator.ts +++ b/worker/utils/idGenerator.ts @@ -3,6 +3,12 @@ * Simple wrapper around crypto.randomUUID() for consistent ID generation */ +import { nanoid } from "nanoid"; + export function generateId(): string { return crypto.randomUUID(); +} + +export function generateNanoId(): string { + return nanoid(); } \ No newline at end of file From 6bdd698d1adae29c2e0e793242964dee7b91dacb Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 24 Oct 2025 01:13:06 -0400 Subject: [PATCH 069/150] refactor: migrate sandbox proxy to internal service and revert sessionId to uuid --- worker/agents/core/simpleGeneratorAgent.ts | 3 +- .../implementations/DeploymentManager.ts | 14 +- worker/index.ts | 2 +- worker/services/sandbox/request-handler.ts | 134 ++++++++++++++++++ 4 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 worker/services/sandbox/request-handler.ts diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index d791ce03..45d19608 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -48,7 +48,6 @@ import { ConversationMessage, ConversationState } from '../inferutils/common'; import { DeepCodeDebugger } from '../assistants/codeDebugger'; import { DeepDebugResult } from './types'; import { StateMigration } from './stateMigration'; -import { generateNanoId } from 'worker/utils/idGenerator'; interface Operations { codeReview: CodeReviewOperation; @@ -188,7 +187,7 @@ export class SimpleCodeGeneratorAgent extends Agent { ): Promise { const { query, language, frameworks, hostname, inferenceContext, templateInfo } = initArgs; - const sandboxSessionId = generateNanoId(); + const sandboxSessionId = DeploymentManager.generateNewSessionId(); this.initLogger(inferenceContext.agentId, sandboxSessionId, inferenceContext.userId); // Generate a blueprint diff --git a/worker/agents/services/implementations/DeploymentManager.ts b/worker/agents/services/implementations/DeploymentManager.ts index f91d8f8d..7e63b3a7 100644 --- a/worker/agents/services/implementations/DeploymentManager.ts +++ b/worker/agents/services/implementations/DeploymentManager.ts @@ -8,7 +8,7 @@ import { import { BootstrapResponse, GitHubPushRequest, StaticAnalysisResponse, RuntimeError, PreviewType } from '../../../services/sandbox/sandboxTypes'; import { GitHubExportResult } from '../../../services/github/types'; import { FileOutputType } from '../../schemas'; -import { generateId, generateNanoId } from '../../../utils/idGenerator'; +import { generateId } from '../../../utils/idGenerator'; import { generateAppProxyToken, generateAppProxyUrl } from '../../../services/aigateway-proxy/controller'; import { BaseAgentService } from './BaseAgentService'; import { ServiceOptions } from '../interfaces/IServiceOptions'; @@ -41,7 +41,7 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa if (!state.sessionId) { this.setState({ ...state, - sessionId: this.generateNewSessionId() + sessionId: DeploymentManager.generateNewSessionId() }); } } @@ -78,7 +78,7 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa const logger = this.getLog(); const state = this.getState(); const oldSessionId = state.sessionId; - const newSessionId = this.generateNewSessionId(); + const newSessionId = DeploymentManager.generateNewSessionId(); logger.info(`SessionId reset: ${oldSessionId} → ${newSessionId}`); @@ -93,8 +93,8 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa }); } - private generateNewSessionId(): string { - return generateNanoId(); + static generateNewSessionId(): string { + return generateId(); } /** @@ -399,6 +399,10 @@ export class DeploymentManager extends BaseAgentService implements IDeploymentMa ...this.getState(), sandboxInstanceId: undefined }); + + callbacks?.onError?.({ + error: `Deployment attempt ${attempt} failed: ${errorMsg}` + }); // Exponential backoff before retry (capped at 30 seconds) const backoffMs = Math.min(1000 * Math.pow(2, Math.min(attempt - 1, 5)), 30000); diff --git a/worker/index.ts b/worker/index.ts index 9fea8b63..69967c87 100644 --- a/worker/index.ts +++ b/worker/index.ts @@ -1,6 +1,5 @@ import { createLogger } from './logger'; import { SmartCodeGeneratorAgent } from './agents/core/smartGeneratorAgent'; -import { proxyToSandbox } from '@cloudflare/sandbox'; import { isDispatcherAvailable } from './utils/dispatcherUtils'; import { createApp } from './app'; // import * as Sentry from '@sentry/cloudflare'; @@ -9,6 +8,7 @@ import { DORateLimitStore as BaseDORateLimitStore } from './services/rate-limit/ import { getPreviewDomain } from './utils/urls'; import { proxyToAiGateway } from './services/aigateway-proxy/controller'; import { isOriginAllowed } from './config/security'; +import { proxyToSandbox } from './services/sandbox/request-handler'; // Durable Object and Service exports export { UserAppSandboxService, DeployerService } from './services/sandbox/sandboxSdkClient'; diff --git a/worker/services/sandbox/request-handler.ts b/worker/services/sandbox/request-handler.ts new file mode 100644 index 00000000..d0c6b0db --- /dev/null +++ b/worker/services/sandbox/request-handler.ts @@ -0,0 +1,134 @@ +/* +This code is borrowed from Cloudflare Sandbox-sdk's npm package +*/ + +import { createObjectLogger } from "../../logger"; +import { getSandbox, type Sandbox } from "@cloudflare/sandbox"; + +const logger = createObjectLogger({ + component: 'sandbox-do', + operation: 'proxy' +}); + +export interface SandboxEnv { + Sandbox: DurableObjectNamespace; +} + +export interface RouteInfo { + port: number; + sandboxId: string; + path: string; + token: string; +} + +export async function proxyToSandbox( + request: Request, + env: E +): Promise { + try { + const url = new URL(request.url); + const routeInfo = extractSandboxRoute(url); + + if (!routeInfo) { + return null; // Not a request to an exposed container port + } + + const { sandboxId, port, path, token } = routeInfo; + const sandbox = getSandbox(env.Sandbox, sandboxId); + + // Build proxy request with proper headers + let proxyUrl: string; + + // Route based on the target port + if (port !== 3000) { + // Route directly to user's service on the specified port + proxyUrl = `http://localhost:${port}${path}${url.search}`; + } else { + // Port 3000 is our control plane - route normally + proxyUrl = `http://localhost:3000${path}${url.search}`; + } + + const proxyRequest = new Request(proxyUrl, { + method: request.method, + headers: { + ...Object.fromEntries(request.headers), + 'X-Original-URL': request.url, + 'X-Forwarded-Host': url.hostname, + 'X-Forwarded-Proto': url.protocol.replace(':', ''), + 'X-Sandbox-Name': sandboxId, // Pass the friendly name + }, + body: request.body, + // @ts-expect-error - duplex required for body streaming in modern runtimes + duplex: 'half', + }); + + logger.info('Proxying request to sandbox', { + sandboxId, + port, + path, + token, + proxyUrl, + }); + + return sandbox.containerFetch(proxyRequest, port); + } catch (error) { + logger.error('Proxy routing error', error instanceof Error ? error : new Error(String(error))); + return new Response('Proxy routing error', { status: 500 }); + } +} + +function extractSandboxRoute(url: URL): RouteInfo | null { + // Parse subdomain pattern: port-sandboxId-token.domain (tokens mandatory) + // Token is always exactly 16 chars (generated by generatePortToken) + const subdomainMatch = url.hostname.match(/^(\d{4,5})-([^.-][^.]*?[^.-]|[^.-])-([a-z0-9_-]{16})\.(.+)$/); + + if (!subdomainMatch) { + return null; + } + + const portStr = subdomainMatch[1]; + const sandboxId = subdomainMatch[2]; + const token = subdomainMatch[3]; // Mandatory token + + const port = parseInt(portStr, 10); + + // DNS subdomain length limit is 63 characters + if (sandboxId.length > 63) { + return null; + } + + return { + port, + sandboxId, + path: url.pathname || "/", + token, + }; +} + +export function isLocalhostPattern(hostname: string): boolean { + // Handle IPv6 addresses in brackets (with or without port) + if (hostname.startsWith('[')) { + if (hostname.includes(']:')) { + // [::1]:port format + const ipv6Part = hostname.substring(0, hostname.indexOf(']:') + 1); + return ipv6Part === '[::1]'; + } else { + // [::1] format without port + return hostname === '[::1]'; + } + } + + // Handle bare IPv6 without brackets + if (hostname === '::1') { + return true; + } + + // For IPv4 and regular hostnames, split on colon to remove port + const hostPart = hostname.split(":")[0]; + + return ( + hostPart === "localhost" || + hostPart === "127.0.0.1" || + hostPart === "0.0.0.0" + ); +} From 0b12d76b7fbea3d7ea9e7863e9bb6be0d0cde27f Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 24 Oct 2025 01:19:45 -0400 Subject: [PATCH 070/150] feat: add log file extractor tool and nanoid dependency --- .gitignore | 3 +- bun.lock | 3 +- debug-tools/extract_serialized_files.py | 372 ++++++++++++++++++++++++ package.json | 1 + 4 files changed, 377 insertions(+), 2 deletions(-) create mode 100644 debug-tools/extract_serialized_files.py diff --git a/.gitignore b/.gitignore index d7dee4ae..21292da0 100644 --- a/.gitignore +++ b/.gitignore @@ -47,4 +47,5 @@ debugging.ipynb debug-tools/*.json # Sentry Config File .env.sentry-build-plugin -data-dump \ No newline at end of file +data-dump +debug-tools/extracted \ No newline at end of file diff --git a/bun.lock b/bun.lock index 05528671..485f0d1f 100644 --- a/bun.lock +++ b/bun.lock @@ -67,6 +67,7 @@ "latest": "^0.2.0", "lucide-react": "^0.541.0", "monaco-editor": "^0.52.2", + "nanoid": "^5.1.6", "next-themes": "^0.4.6", "node-fetch": "^3.3.2", "openai": "^5.23.1", @@ -121,7 +122,7 @@ "vite-plugin-monaco-editor": "^1.1.0", "vite-plugin-node-polyfills": "^0.24.0", "vitest": "^3.2.4", - "wrangler": "^4.45.0", + "wrangler": "4.45.0", }, }, }, diff --git a/debug-tools/extract_serialized_files.py b/debug-tools/extract_serialized_files.py new file mode 100644 index 00000000..912f4e26 --- /dev/null +++ b/debug-tools/extract_serialized_files.py @@ -0,0 +1,372 @@ +#!/usr/bin/env python3 +""" +Extract Serialized Files from AI Gateway Logs + +This tool extracts serialized files from OpenAI API request/response logs, +saves them to organized folders, and generates detailed reports comparing +the actual extracted files with the serialized file tree metadata. + +Usage: + python extract_serialized_files.py [ ...] + +Example: + python extract_serialized_files.py ai-gateway-log-01K89AYMH3N5TK81SM9N8EJKHB.json +""" + +import json +import os +import re +import sys +from pathlib import Path +from typing import Dict, List, Tuple, Optional +from collections import defaultdict + + +class FileExtractor: + """Extract files from bash heredoc format in API logs""" + + def __init__(self, log_path: str): + self.log_path = Path(log_path) + self.log_data = self._load_log() + self.metadata = self.log_data.get('metadata', {}) + self.chat_id = self.metadata.get('chatId', 'unknown') + self.action_key = self.metadata.get('actionKey', 'unknown') + self.output_dir = Path(f'debug-tools/extracted/{self.action_key}_{self.chat_id}') + + def _load_log(self) -> dict: + """Load and parse the JSON log file""" + with open(self.log_path, 'r') as f: + return json.load(f) + + def _extract_files_from_text(self, text: str, source: str) -> Dict[str, str]: + """ + Extract files from bash heredoc format. + Format: cat > filepath << 'EOF'\n...content...\nEOF + """ + files = {} + + # Pattern to match: cat > filepath << 'EOF' ... EOF + # Using re.DOTALL to match across newlines + pattern = r"cat > ([^\s<]+) << 'EOF'\n(.*?)\nEOF" + matches = re.finditer(pattern, text, re.DOTALL) + + for match in matches: + filepath = match.group(1) + content = match.group(2) + + # Handle duplicate files by appending a counter + original_filepath = filepath + counter = 1 + while filepath in files: + base, ext = os.path.splitext(original_filepath) + filepath = f"{base}_v{counter}{ext}" + counter += 1 + + files[filepath] = content + print(f" [{source}] Extracted: {filepath} ({len(content)} bytes)") + + return files + + def _extract_structured_files(self, text: str, source: str) -> Dict[str, str]: + """ + Extract files from structured format used in blueprint requests. + Format: #### filePath\n\n```\npath/to/file\n```\n\n#### fileContents\n\n```\ncontent\n``` + """ + files = {} + + # Pattern to match structured file format + pattern = r'#### filePath\s*```\s*([^`]+?)\s*```\s*#### fileContents\s*```(?:[a-z]*\n)?(.*?)```' + matches = re.finditer(pattern, text, re.DOTALL) + + for match in matches: + filepath = match.group(1).strip() + content = match.group(2) + + # Handle duplicate files by appending a counter + original_filepath = filepath + counter = 1 + while filepath in files: + base, ext = os.path.splitext(original_filepath) + filepath = f"{base}_v{counter}{ext}" + counter += 1 + + files[filepath] = content + print(f" [{source}] Extracted: {filepath} ({len(content)} bytes)") + + return files + + def _extract_file_tree_from_text(self, text: str) -> Optional[str]: + """Extract the file tree metadata from the request""" + # Look for file tree section + patterns = [ + r'(.*?)', + r'(.*?)', + r'(.*?)', + ] + + for pattern in patterns: + match = re.search(pattern, text, re.DOTALL) + if match: + return match.group(1).strip() + + return None + + def _build_actual_tree(self, files: Dict[str, str]) -> str: + """Build a tree representation of actually extracted files""" + if not files: + return "No files extracted" + + # Group files by directory + tree = defaultdict(list) + for filepath in sorted(files.keys()): + parts = Path(filepath).parts + if len(parts) == 1: + tree['.'].append(filepath) + else: + directory = str(Path(*parts[:-1])) + filename = parts[-1] + tree[directory].append(filename) + + # Build tree string + lines = [] + for directory in sorted(tree.keys()): + if directory != '.': + lines.append(f"{directory}/") + for filename in sorted(tree[directory]): + if directory == '.': + lines.append(f" {filename}") + else: + lines.append(f" └── {filename}") + + return '\n'.join(lines) + + def _save_files(self, files: Dict[str, str], subdirectory: str = ''): + """Save extracted files to disk""" + if not files: + print(f" No files to save in {subdirectory or 'root'}") + return + + base_dir = self.output_dir / subdirectory if subdirectory else self.output_dir + base_dir.mkdir(parents=True, exist_ok=True) + + for filepath, content in files.items(): + output_path = base_dir / filepath + output_path.parent.mkdir(parents=True, exist_ok=True) + + with open(output_path, 'w', encoding='utf-8') as f: + f.write(content) + + print(f" Saved {len(files)} files to {base_dir}") + + def _generate_report(self, request_files: Dict[str, str], + response_files: Dict[str, str], + serialized_tree: Optional[str]) -> str: + """Generate a detailed comparison report""" + lines = [ + "=" * 80, + f"FILE EXTRACTION REPORT", + "=" * 80, + "", + "## Metadata", + f" Chat ID: {self.chat_id}", + f" Action Key: {self.action_key}", + f" Log File: {self.log_path.name}", + f" Output Dir: {self.output_dir}", + "", + "=" * 80, + "## REQUEST FILES (Serialized Input)", + "=" * 80, + "", + ] + + if request_files: + lines.append(f"Total files extracted from request: {len(request_files)}") + lines.append("") + lines.append("### File List:") + for filepath, content in sorted(request_files.items()): + lines.append(f" - {filepath} ({len(content):,} bytes)") + lines.append("") + lines.append("### Actual File Tree (from extracted files):") + lines.append(self._build_actual_tree(request_files)) + else: + lines.append("No files found in request") + + lines.extend([ + "", + "=" * 80, + "## RESPONSE FILES (AI Generated)", + "=" * 80, + "", + ]) + + if response_files: + lines.append(f"Total files generated in response: {len(response_files)}") + lines.append("") + lines.append("### File List:") + for filepath, content in sorted(response_files.items()): + lines.append(f" - {filepath} ({len(content):,} bytes)") + lines.append("") + lines.append("### Actual File Tree (from generated files):") + lines.append(self._build_actual_tree(response_files)) + else: + lines.append("No files found in response") + + if serialized_tree: + lines.extend([ + "", + "=" * 80, + "## SERIALIZED FILE TREE (from request metadata)", + "=" * 80, + "", + serialized_tree, + ]) + + lines.extend([ + "", + "=" * 80, + "## COMPARISON", + "=" * 80, + "", + ]) + + # Compare counts + lines.append(f"Request files: {len(request_files)}") + lines.append(f"Response files: {len(response_files)}") + lines.append(f"Total files: {len(request_files) + len(response_files)}") + + # Check for duplicates + common_files = set(request_files.keys()) & set(response_files.keys()) + if common_files: + lines.append("") + lines.append(f"⚠️ WARNING: {len(common_files)} files appear in both request and response:") + for filepath in sorted(common_files): + lines.append(f" - {filepath}") + + lines.extend([ + "", + "=" * 80, + "## SUMMARY", + "=" * 80, + "", + f"✓ Extraction completed successfully", + f"✓ Files saved to: {self.output_dir}", + f"✓ Report saved to: {self.output_dir}/REPORT.md", + "", + ]) + + return '\n'.join(lines) + + def _parse_json_content(self, content: str, is_request: bool = False) -> str: + """Parse JSON-encoded content if needed""" + try: + # Try to parse as JSON in case it's double-encoded + parsed = json.loads(content) + if isinstance(parsed, dict): + if is_request: + # For requests, content is in messages array + if 'messages' in parsed: + # Combine all message contents + all_content = [] + for msg in parsed['messages']: + if 'content' in msg: + all_content.append(msg['content']) + return '\n\n'.join(all_content) + else: + # For responses, content is in choices[0].delta.content or choices[0].message.content + if 'choices' in parsed and len(parsed['choices']) > 0: + choice = parsed['choices'][0] + if 'delta' in choice and 'content' in choice['delta']: + return choice['delta']['content'] + elif 'message' in choice and 'content' in choice['message']: + return choice['message']['content'] + elif isinstance(parsed, str): + return parsed + return content + except (json.JSONDecodeError, KeyError, IndexError): + return content + + def extract_all(self): + """Main extraction method""" + print(f"\n{'='*80}") + print(f"Processing: {self.log_path.name}") + print(f"Action: {self.action_key} | Chat ID: {self.chat_id}") + print(f"{'='*80}\n") + + # Extract files from request + print("Extracting files from REQUEST...") + request_head = self._parse_json_content(self.log_data.get('request_head', ''), is_request=True) + request_files = self._extract_files_from_text(request_head, 'REQUEST') + # Also try structured format + structured_files = self._extract_structured_files(request_head, 'REQUEST') + request_files.update(structured_files) + serialized_tree = self._extract_file_tree_from_text(request_head) + + # Extract files from response + print("\nExtracting files from RESPONSE...") + response_head = self._parse_json_content(self.log_data.get('response_head', ''), is_request=False) + response_files = self._extract_files_from_text(response_head, 'RESPONSE') + # Also try structured format + structured_response = self._extract_structured_files(response_head, 'RESPONSE') + response_files.update(structured_response) + + # Save files + print("\nSaving extracted files...") + if request_files: + self._save_files(request_files, 'request') + if response_files: + self._save_files(response_files, 'response') + + # Generate report + print("\nGenerating report...") + report = self._generate_report(request_files, response_files, serialized_tree) + + # Save report + report_path = self.output_dir / 'REPORT.md' + report_path.parent.mkdir(parents=True, exist_ok=True) + with open(report_path, 'w') as f: + f.write(report) + + print(report) + + return len(request_files) + len(response_files) + + +def main(): + """Main entry point""" + if len(sys.argv) < 2: + print(__doc__) + print("\nError: No log files provided") + sys.exit(1) + + log_files = sys.argv[1:] + total_files = 0 + + print("\n" + "="*80) + print("AI GATEWAY LOG FILE EXTRACTOR") + print("="*80) + print(f"\nProcessing {len(log_files)} log file(s)...\n") + + for log_file in log_files: + if not Path(log_file).exists(): + print(f"❌ Error: File not found: {log_file}") + continue + + try: + extractor = FileExtractor(log_file) + count = extractor.extract_all() + total_files += count + except Exception as e: + print(f"❌ Error processing {log_file}: {e}") + import traceback + traceback.print_exc() + + print("\n" + "="*80) + print(f"EXTRACTION COMPLETE") + print("="*80) + print(f"\nTotal files extracted: {total_files}") + print(f"Output directory: debug-tools/extracted/") + print("\n") + + +if __name__ == '__main__': + main() diff --git a/package.json b/package.json index 0c04c021..83d45889 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "latest": "^0.2.0", "lucide-react": "^0.541.0", "monaco-editor": "^0.52.2", + "nanoid": "^5.1.6", "next-themes": "^0.4.6", "node-fetch": "^3.3.2", "openai": "^5.23.1", From 2156e89a22280fc7d16483e9d50ec635f0bfb2a0 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 24 Oct 2025 15:07:04 -0400 Subject: [PATCH 071/150] feat: DO based git, fs storage, commit every saved file --- worker/agents/core/simpleGeneratorAgent.ts | 22 ++- worker/agents/git/fs-adapter.ts | 172 ++++++++++++++++++ worker/agents/git/git.ts | 153 ++++++++++++++++ worker/agents/git/index.ts | 7 + .../services/implementations/FileManager.ts | 16 +- .../services/interfaces/IFileManager.ts | 4 +- 6 files changed, 360 insertions(+), 14 deletions(-) create mode 100644 worker/agents/git/fs-adapter.ts create mode 100644 worker/agents/git/git.ts create mode 100644 worker/agents/git/index.ts diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index 45d19608..d9a83888 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -48,6 +48,7 @@ import { ConversationMessage, ConversationState } from '../inferutils/common'; import { DeepCodeDebugger } from '../assistants/codeDebugger'; import { DeepDebugResult } from './types'; import { StateMigration } from './stateMigration'; +import { GitVersionControl } from '../git'; interface Operations { codeReview: CodeReviewOperation; @@ -80,6 +81,7 @@ export class SimpleCodeGeneratorAgent extends Agent { protected codingAgent: CodingAgentInterface = new CodingAgentInterface(this); protected deploymentManager!: DeploymentManager; + protected git: GitVersionControl; private previewUrlCache: string = ''; private templateDetailsCache: TemplateDetails | null = null; @@ -159,9 +161,12 @@ export class SimpleCodeGeneratorAgent extends Agent { () => this.state, (s) => this.setState(s) ); - + + // Initialize GitVersionControl + this.git = new GitVersionControl(this.sql); + // Initialize FileManager - this.fileManager = new FileManager(this.stateManager, () => this.getTemplateDetails()); + this.fileManager = new FileManager(this.stateManager, () => this.getTemplateDetails(), this.git); // Initialize DeploymentManager first (manages sandbox client caching) // DeploymentManager will use its own getClient() override for caching @@ -260,6 +265,7 @@ export class SimpleCodeGeneratorAgent extends Agent { async onStart(_props?: Record | undefined): Promise { this.logger().info(`Agent ${this.getAgentId()} session: ${this.state.sessionId} onStart`); + await this.git.init(); // Ignore if agent not initialized if (!this.state.templateName?.trim()) { this.logger().info(`Agent ${this.getAgentId()} session: ${this.state.sessionId} not initialized, ignoring onStart`); @@ -554,7 +560,7 @@ export class SimpleCodeGeneratorAgent extends Agent { const readme = await this.operations.implementPhase.generateReadme(this.getOperationOptions()); - this.fileManager.saveGeneratedFile(readme); + this.fileManager.saveGeneratedFile(readme, "feat: README.md"); this.broadcast(WebSocketMessageResponses.FILE_GENERATED, { message: 'README.md generated successfully', @@ -1138,7 +1144,7 @@ export class SimpleCodeGeneratorAgent extends Agent { }); // Update state with completed phase - this.fileManager.saveGeneratedFiles(finalFiles); + this.fileManager.saveGeneratedFiles(finalFiles, `feat: ${phase.name}`); this.logger().info("Files generated for phase:", phase.name, finalFiles.map(f => f.filePath)); @@ -1303,7 +1309,7 @@ export class SimpleCodeGeneratorAgent extends Agent { this.getOperationOptions() ); - const fileState = this.fileManager.saveGeneratedFile(result); + const fileState = this.fileManager.saveGeneratedFile(result, `fix: ${file.filePath}`); this.broadcast(WebSocketMessageResponses.FILE_REGENERATED, { message: `Regenerated file: ${file.filePath}`, @@ -1414,7 +1420,7 @@ export class SimpleCodeGeneratorAgent extends Agent { }, this.getOperationOptions()); if (fastCodeFixer.length > 0) { - this.fileManager.saveGeneratedFiles(fastCodeFixer); + this.fileManager.saveGeneratedFiles(fastCodeFixer, "fix: Fast smart code fixes"); await this.deployToSandbox(fastCodeFixer); this.logger().info("Fast smart code fixes applied successfully"); } @@ -1486,7 +1492,7 @@ export class SimpleCodeGeneratorAgent extends Agent { filePurpose: allFiles.find(f => f.filePath === file.filePath)?.filePurpose || '', fileContents: file.fileContents })); - this.fileManager.saveGeneratedFiles(fixedFiles); + this.fileManager.saveGeneratedFiles(fixedFiles, "fix: applied deterministic fixes"); await this.deployToSandbox(fixedFiles, false, "fix: applied deterministic fixes"); this.logger().info("Deployed deterministic fixes to sandbox"); @@ -2017,7 +2023,7 @@ export class SimpleCodeGeneratorAgent extends Agent { '[cloudflarebutton]', prepareCloudflareButton(options.repositoryHtmlUrl, 'markdown') ); - this.fileManager.saveGeneratedFile(readmeFile); + this.fileManager.saveGeneratedFile(readmeFile, "feat: README updated with Cloudflare deploy button"); this.logger().info('README prepared with Cloudflare deploy button'); // Deploy updated README to sandbox so it's visible in preview diff --git a/worker/agents/git/fs-adapter.ts b/worker/agents/git/fs-adapter.ts new file mode 100644 index 00000000..b4441219 --- /dev/null +++ b/worker/agents/git/fs-adapter.ts @@ -0,0 +1,172 @@ +/** + * SQLite filesystem adapter for isomorphic-git + * One DO = one Git repo, stored directly in SQLite + * + * Limits: + * - Cloudflare DO SQLite: 10GB total storage + * - Max parameter size: ~1MB per SQL statement parameter + * - Git objects are base64-encoded to safely store binary data + */ + +export interface SqlExecutor { + (query: TemplateStringsArray, ...values: (string | number | boolean | null)[]): T[]; +} + +// 1MB limit for Cloudflare DO SQL parameters, leave some headroom +const MAX_OBJECT_SIZE = 900 * 1024; // 900KB + +export class SqliteFS { + constructor(private sql: SqlExecutor) {} + + /** + * Get storage statistics for observability + */ + getStorageStats(): { totalObjects: number; totalBytes: number; largestObject: { path: string; size: number } | null } { + const objects = this.sql<{ path: string; data: string }>`SELECT path, data FROM git_objects`; + + if (!objects || objects.length === 0) { + return { totalObjects: 0, totalBytes: 0, largestObject: null }; + } + + let totalBytes = 0; + let largestObject: { path: string; size: number } | null = null; + + for (const obj of objects) { + const size = obj.data.length; // Base64 encoded size + totalBytes += size; + + if (!largestObject || size > largestObject.size) { + largestObject = { path: obj.path, size }; + } + } + + return { + totalObjects: objects.length, + totalBytes, + largestObject + }; + } + + init(): void { + this.sql` + CREATE TABLE IF NOT EXISTS git_objects ( + path TEXT PRIMARY KEY, + data TEXT NOT NULL, + mtime INTEGER NOT NULL + ) + `; + + // Create index for efficient directory listings + this.sql`CREATE INDEX IF NOT EXISTS idx_git_objects_path ON git_objects(path)`; + } + + readFile(path: string, options?: { encoding?: 'utf8' }): Uint8Array | string { + // Normalize path (remove leading slashes) + const normalized = path.replace(/^\/+/, ''); + const result = this.sql<{ data: string }>`SELECT data FROM git_objects WHERE path = ${normalized}`; + if (!result[0]) throw new Error(`ENOENT: ${path}`); + + const base64Data = result[0].data; + + // Decode from base64 + const binaryString = atob(base64Data); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + return options?.encoding === 'utf8' ? new TextDecoder().decode(bytes) : bytes; + } + + writeFile(path: string, data: Uint8Array | string): void { + // Normalize path (remove leading slashes) + const normalized = path.replace(/^\/+/, ''); + + // Convert to Uint8Array if string + const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data; + + // Check size limit + if (bytes.length > MAX_OBJECT_SIZE) { + throw new Error(`File too large: ${path} (${bytes.length} bytes, max ${MAX_OBJECT_SIZE})`); + } + + // Encode to base64 for safe storage + let binaryString = ''; + for (let i = 0; i < bytes.length; i++) { + binaryString += String.fromCharCode(bytes[i]); + } + const base64Content = btoa(binaryString); + + this.sql`INSERT OR REPLACE INTO git_objects (path, data, mtime) VALUES (${normalized}, ${base64Content}, ${Date.now()})`; + + // Only log if approaching size limit (no overhead for normal files) + if (bytes.length > MAX_OBJECT_SIZE * 0.8) { + console.warn(`[Git Storage] Large file: ${normalized} is ${(bytes.length / 1024).toFixed(1)}KB (limit: ${(MAX_OBJECT_SIZE / 1024).toFixed(1)}KB)`); + } + } + + unlink(path: string): void { + // Normalize path (remove leading slashes) + const normalized = path.replace(/^\/+/, ''); + this.sql`DELETE FROM git_objects WHERE path = ${normalized}`; + } + + readdir(path: string): string[] { + // Normalize path (remove leading/trailing slashes) + const normalized = path.replace(/^\/+|\/+$/g, ''); + + let result; + if (normalized === '') { + // Root directory - get all paths + result = this.sql<{ path: string }>`SELECT path FROM git_objects`; + } else { + // Subdirectory - match prefix + result = this.sql<{ path: string }>`SELECT path FROM git_objects WHERE path LIKE ${normalized + '/%'}`; + } + + if (!result || result.length === 0) return []; + + const children = new Set(); + const prefixLen = normalized ? normalized.length + 1 : 0; + + for (const row of result) { + const relativePath = normalized ? row.path.substring(prefixLen) : row.path; + const first = relativePath.split('/')[0]; + if (first) children.add(first); + } + + return Array.from(children); + } + + mkdir(_path: string): void { + // No-op: directories are implicit in Git + } + + rmdir(path: string): void { + // Normalize path (remove leading/trailing slashes) + const normalized = path.replace(/^\/+|\/+$/g, ''); + this.sql`DELETE FROM git_objects WHERE path LIKE ${normalized + '%'}`; + } + + stat(path: string): { type: 'file' | 'dir'; mode: number; size: number; mtimeMs: number } { + // Normalize path (remove leading slashes) + const normalized = path.replace(/^\/+/, ''); + const result = this.sql<{ data: string; mtime: number }>`SELECT data, mtime FROM git_objects WHERE path = ${normalized}`; + if (!result[0]) throw new Error(`ENOENT: ${path}`); + + const row = result[0]; + return { type: 'file', mode: 0o100644, size: row.data.length, mtimeMs: row.mtime }; + } + + lstat(path: string) { + return this.stat(path); + } + + symlink(target: string, path: string): void { + this.writeFile(path, target); + } + + readlink(path: string): string { + return this.readFile(path, { encoding: 'utf8' }) as string; + } +} \ No newline at end of file diff --git a/worker/agents/git/git.ts b/worker/agents/git/git.ts new file mode 100644 index 00000000..f36fd249 --- /dev/null +++ b/worker/agents/git/git.ts @@ -0,0 +1,153 @@ +/** + * Git version control for Durable Objects using isomorphic-git + */ + +import git from 'isomorphic-git'; +import { SqliteFS, type SqlExecutor } from './fs-adapter'; +import { FileOutputType } from '../schemas'; + +export interface CommitInfo { + oid: string; + message: string; + author: string; + timestamp: number; +} + +type FileSnapshot = Omit; + +export class GitVersionControl { + private fs: SqliteFS; + private author: { name: string; email: string }; + + constructor(sql: SqlExecutor, author?: { name: string; email: string }) { + this.fs = new SqliteFS(sql); + this.author = author || { name: 'Vibesdk', email: 'vibesdk-bot@cloudflare.com' }; + + // Initialize SQLite table synchronously + this.fs.init(); + } + + async init(): Promise { + // Initialize git repository (isomorphic-git operations are async) + try { + console.log('Checking for git repository...'); + this.fs.readdir('/.git'); + console.log('Git repository found'); + } catch { + console.log('Git repository not found, initializing...'); + try { + await git.init({ fs: this.fs, dir: '/', defaultBranch: 'main' }); + } catch (error) { + console.error(`Failed to initialize git repository:`, error); + } + } + } + + async commit(files: FileSnapshot[], message?: string): Promise { + if (!files.length) throw new Error('Cannot create empty commit'); + + // Normalize paths (remove leading slashes for git) + const normalizedFiles = files.map(f => ({ + path: f.filePath.startsWith('/') ? f.filePath.slice(1) : f.filePath, + content: f.fileContents + })); + + // Write and stage files first + for (const file of normalizedFiles) { + try { + this.fs.writeFile(file.path, file.content); // Synchronous + await git.add({ fs: this.fs, dir: '/', filepath: file.path }); + } catch (error) { + throw new Error(`Failed to write/stage file ${file.path}: ${error instanceof Error ? error.message : String(error)}`); + } + } + + // Check if there are actual changes (compare staged vs HEAD) + let hasChanges = false; + try { + const status = await git.statusMatrix({ fs: this.fs, dir: '/' }); + // row[1] = HEAD index, row[2] = STAGE index + // If they differ, we have changes to commit + hasChanges = status.some(row => row[1] !== row[2]); + } catch (e) { + // First commit or error, assume changes + hasChanges = true; + } + + if (!hasChanges) { + return null; // No actual changes to commit + } + + return git.commit({ + fs: this.fs, + dir: '/', + message: message || `Auto-checkpoint (${new Date().toISOString()})`, + author: { + name: this.author.name, + email: this.author.email, + timestamp: Math.floor(Date.now() / 1000) + } + }); + } + + async log(limit = 50): Promise { + try { + const commits = await git.log({ fs: this.fs, dir: '/', depth: limit, ref: 'main' }); + return commits.map(c => ({ + oid: c.oid, + message: c.commit.message, + author: `${c.commit.author.name} <${c.commit.author.email}>`, + timestamp: c.commit.author.timestamp * 1000 + })); + } catch { + return []; + } + } + + async checkout(oid: string): Promise { + const { commit } = await git.readCommit({ fs: this.fs, dir: '/', oid }); + const files: FileSnapshot[] = []; + await this.walkTree(commit.tree, '', files); + return files; + } + + private async walkTree(treeOid: string, prefix: string, files: FileSnapshot[]): Promise { + const { tree } = await git.readTree({ fs: this.fs, dir: '/', oid: treeOid }); + + for (const entry of tree) { + const path = prefix ? `${prefix}/${entry.path}` : entry.path; + + if (entry.type === 'blob') { + const { blob } = await git.readBlob({ fs: this.fs, dir: '/', oid: entry.oid }); + // Git blobs are binary, decode with proper error handling + try { + const content = new TextDecoder('utf-8').decode(blob); + // Check if it's valid text by looking for null bytes + if (!content.includes('\0')) { + files.push({ filePath: path, fileContents: content }); + } + // Skip binary files (checkout is for reverting code files) + } catch { + // Failed to decode, skip binary file + } + } else if (entry.type === 'tree') { + await this.walkTree(entry.oid, path, files); + } + } + } + + async getHead(): Promise { + try { + return await git.resolveRef({ fs: this.fs, dir: '/', ref: 'HEAD' }); + } catch { + return null; + } + } + + /** + * Get storage statistics for monitoring and observability + */ + getStorageStats(): { totalObjects: number; totalBytes: number; largestObject: { path: string; size: number } | null } { + return this.fs.getStorageStats(); + } +} \ No newline at end of file diff --git a/worker/agents/git/index.ts b/worker/agents/git/index.ts new file mode 100644 index 00000000..55ec6c1a --- /dev/null +++ b/worker/agents/git/index.ts @@ -0,0 +1,7 @@ +/** + * Git version control for Durable Objects + */ + +export { GitVersionControl } from './git'; +export type { CommitInfo } from './git'; +export type { SqlExecutor } from './fs-adapter'; \ No newline at end of file diff --git a/worker/agents/services/implementations/FileManager.ts b/worker/agents/services/implementations/FileManager.ts index a1d920d1..1a9bd50f 100644 --- a/worker/agents/services/implementations/FileManager.ts +++ b/worker/agents/services/implementations/FileManager.ts @@ -5,6 +5,7 @@ import { FileOutputType } from '../../schemas'; import { FileProcessing } from '../../domain/pure/FileProcessing'; import { FileState } from 'worker/agents/core/state'; import { TemplateDetails } from '../../../services/sandbox/sandboxTypes'; +import { GitVersionControl } from 'worker/agents/git'; /** * Manages file operations for code generation @@ -13,7 +14,8 @@ import { TemplateDetails } from '../../../services/sandbox/sandboxTypes'; export class FileManager implements IFileManager { constructor( private stateManager: IStateManager, - private getTemplateDetailsFunc: () => TemplateDetails + private getTemplateDetailsFunc: () => TemplateDetails, + private git: GitVersionControl ) {} getGeneratedFile(path: string): FileOutputType | null { @@ -36,11 +38,11 @@ export class FileManager implements IFileManager { return FileProcessing.getAllFiles(this.getTemplateDetailsFunc(), state.generatedFilesMap); } - saveGeneratedFile(file: FileOutputType): FileState { - return this.saveGeneratedFiles([file])[0]; + saveGeneratedFile(file: FileOutputType, commitMessage: string): FileState { + return this.saveGeneratedFiles([file], commitMessage)[0]; } - saveGeneratedFiles(files: FileOutputType[]): FileState[] { + saveGeneratedFiles(files: FileOutputType[], commitMessage: string): FileState[] { const filesMap = { ...this.stateManager.getState().generatedFilesMap }; const fileStates: FileState[] = []; @@ -80,6 +82,12 @@ export class FileManager implements IFileManager { ...this.stateManager.getState(), generatedFilesMap: filesMap }); + + try { + this.git.commit(fileStates, commitMessage); + } catch (error) { + console.error(`Failed to commit files:`, error, commitMessage); + } return fileStates; } diff --git a/worker/agents/services/interfaces/IFileManager.ts b/worker/agents/services/interfaces/IFileManager.ts index 02b9af01..2bc27467 100644 --- a/worker/agents/services/interfaces/IFileManager.ts +++ b/worker/agents/services/interfaces/IFileManager.ts @@ -24,12 +24,12 @@ export interface IFileManager { /** * Save a generated file */ - saveGeneratedFile(file: FileOutputType): void; + saveGeneratedFile(file: FileOutputType, commitMessage: string): void; /** * Save multiple generated files */ - saveGeneratedFiles(files: FileOutputType[]): void; + saveGeneratedFiles(files: FileOutputType[], commitMessage: string): void; /** * Delete files from the file manager From 3a108c65942f81a7c45a04f390cc43daaab693ce Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 24 Oct 2025 15:22:18 -0400 Subject: [PATCH 072/150] chore: add isomorphic-git dependency to package.json --- bun.lock | 25 +++++++++++++++++++++++++ package.json | 1 + 2 files changed, 26 insertions(+) diff --git a/bun.lock b/bun.lock index 485f0d1f..e925b322 100644 --- a/bun.lock +++ b/bun.lock @@ -62,6 +62,7 @@ "hono": "^4.9.9", "html2canvas-pro": "^1.5.11", "input-otp": "^1.4.2", + "isomorphic-git": "^1.34.0", "jose": "^5.10.0", "jsonc-parser": "^3.3.1", "latest": "^0.2.0", @@ -1019,6 +1020,8 @@ "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], + "async-lock": ["async-lock@1.4.1", "", {}, "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ=="], + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], @@ -1135,6 +1138,8 @@ "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], + "clean-git-ref": ["clean-git-ref@2.0.1", "", {}, "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw=="], + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "cloudflare": ["cloudflare@4.5.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-fPcbPKx4zF45jBvQ0z7PCdgejVAPBBCZxwqk1k7krQNfpM07Cfj97/Q6wBzvYqlWXx/zt1S9+m8vnfCe06umbQ=="], @@ -1183,6 +1188,8 @@ "cosmiconfig": ["cosmiconfig@8.3.6", "", { "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA=="], + "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], + "create-ecdh": ["create-ecdh@4.0.4", "", { "dependencies": { "bn.js": "^4.1.0", "elliptic": "^6.5.3" } }, "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A=="], "create-hash": ["create-hash@1.2.0", "", { "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", "md5.js": "^1.3.4", "ripemd160": "^2.0.1", "sha.js": "^2.4.0" } }, "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg=="], @@ -1245,6 +1252,8 @@ "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + "dedent": ["dedent@1.7.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ=="], "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], @@ -1281,6 +1290,8 @@ "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="], + "diff3": ["diff3@0.0.3", "", {}, "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g=="], + "diffie-hellman": ["diffie-hellman@5.0.3", "", { "dependencies": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" } }, "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg=="], "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], @@ -1641,6 +1652,8 @@ "is-generator-function": ["is-generator-function@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "get-proto": "^1.0.0", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ=="], + "is-git-ref-name-valid": ["is-git-ref-name-valid@1.0.0", "", {}, "sha512-2hLTg+7IqMSP9nNp/EVCxzvAOJGsAn0f/cKtF8JaBeivjH5UgE/XZo3iJ0AvibdE7KSF1f/7JbjBTB8Wqgbn/w=="], + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], @@ -1683,6 +1696,8 @@ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "isomorphic-git": ["isomorphic-git@1.34.0", "", { "dependencies": { "async-lock": "^1.4.1", "clean-git-ref": "^2.0.1", "crc-32": "^1.2.0", "diff3": "0.0.3", "ignore": "^5.1.4", "is-git-ref-name-valid": "^1.0.0", "minimisted": "^2.0.0", "pako": "^1.0.10", "path-browserify": "^1.0.1", "pify": "^4.0.1", "readable-stream": "^3.4.0", "sha.js": "^2.4.12", "simple-get": "^4.0.1" }, "bin": { "isogit": "cli.cjs" } }, "sha512-J82yRa/4wm9VuOWSlI37I9Sa+n1gWaSWuKQk8zhpo6RqTW+ZTcK5c/KubLMcuVU3Btc+maRCa3YlRKqqY9q7qQ=="], + "isomorphic-timers-promises": ["isomorphic-timers-promises@1.0.1", "", {}, "sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ=="], "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], @@ -1955,6 +1970,8 @@ "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + "miniflare": ["miniflare@4.20251001.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251001.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-OHd31D2LT8JH+85nVXClV0Z18jxirCohzKNAcZs/fgt4mIkUDtidX3VqR3ovAM0jWooNxrFhB9NSs3iDbiJF7Q=="], "minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="], @@ -1965,6 +1982,8 @@ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "minimisted": ["minimisted@2.0.1", "", { "dependencies": { "minimist": "^1.2.5" } }, "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA=="], + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], "minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="], @@ -2097,6 +2116,8 @@ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="], + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], "pkce-challenge": ["pkce-challenge@5.0.0", "", {}, "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ=="], @@ -2279,6 +2300,10 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + "simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="], "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], diff --git a/package.json b/package.json index 83d45889..d6fb6d04 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "hono": "^4.9.9", "html2canvas-pro": "^1.5.11", "input-otp": "^1.4.2", + "isomorphic-git": "^1.34.0", "jose": "^5.10.0", "jsonc-parser": "^3.3.1", "latest": "^0.2.0", From 6005c4dea9fe15ee03f8a77edbba950081dd176e Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 24 Oct 2025 16:10:45 -0400 Subject: [PATCH 073/150] feat: add git clone protocol support with template rebasing --- worker/agents/core/simpleGeneratorAgent.ts | 30 +- worker/agents/git/git-clone-service.ts | 272 ++++++++++++++++++ worker/agents/git/git.ts | 2 +- worker/agents/git/index.ts | 6 +- worker/agents/git/memfs.ts | 144 ++++++++++ .../services/implementations/CodingAgent.ts | 10 +- .../services/interfaces/ICodingAgent.ts | 4 + worker/api/controllers/appView/controller.ts | 185 +++++++++++- worker/api/controllers/appView/types.ts | 10 + worker/api/routes/appRoutes.ts | 12 + 10 files changed, 670 insertions(+), 5 deletions(-) create mode 100644 worker/agents/git/git-clone-service.ts create mode 100644 worker/agents/git/memfs.ts diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index d9a83888..d74fce9a 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -34,6 +34,7 @@ import { InferenceContext, AgentActionKey } from '../inferutils/config.types'; import { AGENT_CONFIG } from '../inferutils/config'; import { ModelConfigService } from '../../database/services/ModelConfigService'; import { fixProjectIssues } from '../../services/code-fixer'; +import { GitVersionControl, GitCloneService } from '../git'; import { FastCodeFixerOperation } from '../operations/PostPhaseCodeFixer'; import { looksLikeCommand } from '../utils/common'; import { generateBlueprint } from '../planning/blueprint'; @@ -48,7 +49,6 @@ import { ConversationMessage, ConversationState } from '../inferutils/common'; import { DeepCodeDebugger } from '../assistants/codeDebugger'; import { DeepDebugResult } from './types'; import { StateMigration } from './stateMigration'; -import { GitVersionControl } from '../git'; interface Operations { codeReview: CodeReviewOperation; @@ -2340,4 +2340,32 @@ export class SimpleCodeGeneratorAgent extends Agent { throw new Error(`Screenshot capture failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } + + /** + * Handle git info/refs request for cloning + * Returns git protocol advertisement + */ + async handleGitInfoRefs(): Promise { + const fs = await GitCloneService.buildRepository({ + agentGitFS: this.git.fs, + templateDetails: this.templateDetailsCache, + appQuery: this.state.query + }); + + return await GitCloneService.handleInfoRefs(fs); + } + + /** + * Handle git upload-pack request for cloning + * Returns packfile for git clone + */ + async handleGitUploadPack(): Promise { + const fs = await GitCloneService.buildRepository({ + agentGitFS: this.git.fs, + templateDetails: this.templateDetailsCache, + appQuery: this.state.query + }); + + return await GitCloneService.handleUploadPack(fs); + } } diff --git a/worker/agents/git/git-clone-service.ts b/worker/agents/git/git-clone-service.ts new file mode 100644 index 00000000..3f7e0f1c --- /dev/null +++ b/worker/agents/git/git-clone-service.ts @@ -0,0 +1,272 @@ +/** + * Git clone service for building and serving repositories + * Handles template rebasing and git HTTP protocol + */ + +import git from 'isomorphic-git'; +import { MemFS } from './memfs'; +import { SqliteFS } from './fs-adapter'; +import { createLogger } from '../../logger'; +import type { TemplateDetails as SandboxTemplateDetails } from '../../services/sandbox/sandboxTypes'; + +const logger = createLogger('GitCloneService'); + +export interface RepositoryBuildOptions { + agentGitFS: SqliteFS; + templateDetails: SandboxTemplateDetails | null | undefined; + appQuery: string; +} + +export class GitCloneService { + /** + * Build in-memory git repository by rebasing agent's git history on template files + * + * Strategy: + * 1. Create base commit with template files + * 2. Export all git objects from agent's repo (SqliteFS) + * 3. Import git objects into MemFS + * 4. Update refs to point to agent's commits + * + * Result: Template base + agent's commit history on top + */ + static async buildRepository(options: RepositoryBuildOptions): Promise { + const { agentGitFS, templateDetails, appQuery } = options; + const fs = new MemFS(); + + try { + logger.info('Building git repository with template rebasing', { + templateName: templateDetails?.name, + templateFileCount: templateDetails ? Object.keys(templateDetails.allFiles).length : 0 + }); + + // Step 1: Create base commit with template files + await git.init({ fs, dir: '/', defaultBranch: 'main' }); + + if (templateDetails?.allFiles) { + // Write template files + for (const [path, content] of Object.entries(templateDetails.allFiles)) { + fs.writeFile(path, content); + } + + // Stage and commit template files + await git.add({ fs, dir: '/', filepath: '.' }); + const templateCommitOid = await git.commit({ + fs, + dir: '/', + message: `Template: ${templateDetails.name}\n\nBase template for Vibesdk application\nQuery: ${appQuery}`, + author: { + name: 'Vibesdk Template', + email: 'templates@cloudflare.dev', + timestamp: Math.floor(Date.now() / 1000) + } + }); + + logger.info('Created template base commit', { oid: templateCommitOid }); + } + + // Step 2: Check if agent has git history + const agentHasGitRepo = await this.hasGitRepository(agentGitFS); + + if (agentHasGitRepo) { + // Step 3: Export all git objects from agent's repo and import to MemFS + await this.copyGitObjects(agentGitFS, fs); + + // Step 4: Get agent's HEAD + try { + const agentHeadOid = await git.resolveRef({ fs: agentGitFS, dir: '/', ref: 'HEAD' }); + + // Update main branch to point to agent's HEAD + // This effectively rebases agent's commits on top of template + await git.writeRef({ + fs, + dir: '/', + ref: 'refs/heads/main', + value: agentHeadOid, + force: true + }); + + logger.info('Rebased agent history on template', { + agentHead: agentHeadOid + }); + } catch (error) { + logger.warn('Could not rebase agent history', { error }); + // Template commit is already in place, continue + } + } else { + logger.info('No agent git history found, using template only'); + } + + logger.info('Git repository built successfully'); + return fs; + } catch (error) { + logger.error('Failed to build git repository', { error }); + throw new Error(`Failed to build repository: ${error instanceof Error ? error.message : String(error)}`); + } + } + + /** + * Check if agent has initialized git repository + */ + private static async hasGitRepository(agentFS: SqliteFS): Promise { + try { + agentFS.readdir('/.git'); + return true; + } catch { + return false; + } + } + + /** + * Copy all git objects from agent's SqliteFS to MemFS + * This includes commits, trees, and blobs + */ + private static async copyGitObjects(sourceFS: SqliteFS, targetFS: MemFS): Promise { + try { + // Copy the entire .git directory structure + await this.copyDirectory(sourceFS, targetFS, '/.git'); + logger.info('Copied git objects from agent to MemFS'); + } catch (error) { + logger.error('Failed to copy git objects', { error }); + throw error; + } + } + + /** + * Recursively copy directory from source to target filesystem + */ + private static async copyDirectory( + sourceFS: SqliteFS, + targetFS: MemFS, + dirPath: string + ): Promise { + const entries = sourceFS.readdir(dirPath); + + for (const entry of entries) { + const fullPath = `${dirPath}/${entry}`; + + try { + const stat = sourceFS.stat(fullPath); + + if (stat.type === 'file') { + // Copy file + const data = sourceFS.readFile(fullPath); + targetFS.writeFile(fullPath, data as Uint8Array); + } else if (stat.type === 'dir') { + // Recursively copy directory + await this.copyDirectory(sourceFS, targetFS, fullPath); + } + } catch (error) { + logger.warn(`Failed to copy ${fullPath}`, { error }); + // Continue with other files + } + } + } + + /** + * Handle git info/refs request + * Returns advertisement of available refs for git clone + */ + static async handleInfoRefs(fs: MemFS): Promise { + try { + const head = await git.resolveRef({ fs, dir: '/', ref: 'HEAD' }); + const branches = await git.listBranches({ fs, dir: '/' }); + + // Git HTTP protocol: info/refs response format + let response = '001e# service=git-upload-pack\n0000'; + + // HEAD ref with capabilities + const headLine = `${head} HEAD\0side-band-64k thin-pack ofs-delta agent=git/isomorphic-git\n`; + response += this.formatPacketLine(headLine); + + // Branch refs + for (const branch of branches) { + const oid = await git.resolveRef({ fs, dir: '/', ref: `refs/heads/${branch}` }); + response += this.formatPacketLine(`${oid} refs/heads/${branch}\n`); + } + + // Flush packet + response += '0000'; + + return response; + } catch (error) { + logger.error('Failed to handle info/refs', { error }); + throw new Error(`Failed to get refs: ${error instanceof Error ? error.message : String(error)}`); + } + } + + /** + * Handle git upload-pack request (actual clone operation) + * Generates and returns packfile for git client + */ + static async handleUploadPack(fs: MemFS): Promise { + try { + // For initial implementation, send all objects + // Future optimization: parse client wants from request body + const head = await git.resolveRef({ fs, dir: '/', ref: 'HEAD' }); + + // Walk the commit tree to get all objects + const { commit } = await git.readCommit({ fs, dir: '/', oid: head }); + const objects = [head, commit.tree]; + + // Recursively collect all tree and blob objects + const collectTreeObjects = async (treeOid: string): Promise => { + objects.push(treeOid); + const { tree } = await git.readTree({ fs, dir: '/', oid: treeOid }); + + for (const entry of tree) { + objects.push(entry.oid); + if (entry.type === 'tree') { + await collectTreeObjects(entry.oid); + } + } + }; + + await collectTreeObjects(commit.tree); + + logger.info('Generating packfile', { objectCount: objects.length }); + + // Create packfile with all objects + const packResult = await git.packObjects({ + fs, + dir: '/', + oids: objects + }); + + // packObjects returns { packfile: Uint8Array } + const packfile = packResult.packfile; + + if (!packfile) { + throw new Error('Failed to generate packfile'); + } + + // Wrap packfile in sideband format for git protocol + return this.wrapInSideband(packfile); + } catch (error) { + logger.error('Failed to handle upload-pack', { error }); + throw new Error(`Failed to generate pack: ${error instanceof Error ? error.message : String(error)}`); + } + } + + /** + * Format git packet line (4-byte hex length + data) + */ + private static formatPacketLine(data: string): string { + const length = data.length + 4; + const hexLength = length.toString(16).padStart(4, '0'); + return hexLength + data; + } + + /** + * Wrap packfile data in sideband format + * Sideband-64k protocol for multiplexing pack data and progress + */ + private static wrapInSideband(packfile: Uint8Array): Uint8Array { + // Simple implementation: send packfile in one sideband message + // Channel 1 = pack data + const header = new Uint8Array([1]); // Sideband channel 1 + const result = new Uint8Array(header.length + packfile.length); + result.set(header, 0); + result.set(packfile, header.length); + return result; + } +} diff --git a/worker/agents/git/git.ts b/worker/agents/git/git.ts index f36fd249..605a8c56 100644 --- a/worker/agents/git/git.ts +++ b/worker/agents/git/git.ts @@ -16,7 +16,7 @@ export interface CommitInfo { type FileSnapshot = Omit; export class GitVersionControl { - private fs: SqliteFS; + public fs: SqliteFS; private author: { name: string; email: string }; constructor(sql: SqlExecutor, author?: { name: string; email: string }) { diff --git a/worker/agents/git/index.ts b/worker/agents/git/index.ts index 55ec6c1a..0814f32a 100644 --- a/worker/agents/git/index.ts +++ b/worker/agents/git/index.ts @@ -3,5 +3,9 @@ */ export { GitVersionControl } from './git'; +export { GitCloneService } from './git-clone-service'; +export { MemFS } from './memfs'; +export { SqliteFS } from './fs-adapter'; export type { CommitInfo } from './git'; -export type { SqlExecutor } from './fs-adapter'; \ No newline at end of file +export type { SqlExecutor } from './fs-adapter'; +export type { RepositoryBuildOptions } from './git-clone-service'; \ No newline at end of file diff --git a/worker/agents/git/memfs.ts b/worker/agents/git/memfs.ts new file mode 100644 index 00000000..b5b0fdab --- /dev/null +++ b/worker/agents/git/memfs.ts @@ -0,0 +1,144 @@ +/** + * In-memory filesystem for git clone operations + * Minimal implementation for isomorphic-git compatibility + */ + +export class MemFS { + private files = new Map(); + + /** + * Write file to memory + */ + writeFile(path: string, data: string | Uint8Array): void { + const bytes = typeof data === 'string' + ? new TextEncoder().encode(data) + : data; + + // Normalize path (remove leading slash for consistency) + const normalized = path.startsWith('/') ? path.slice(1) : path; + this.files.set(normalized, bytes); + } + + /** + * Read file from memory + */ + readFile(path: string, options?: { encoding?: 'utf8' }): Uint8Array | string { + const normalized = path.startsWith('/') ? path.slice(1) : path; + const data = this.files.get(normalized); + + if (!data) { + const error: NodeJS.ErrnoException = new Error(`ENOENT: no such file or directory, open '${path}'`); + error.code = 'ENOENT'; + throw error; + } + + if (options?.encoding === 'utf8') { + return new TextDecoder().decode(data); + } + + return data; + } + + /** + * List directory contents + */ + readdir(dirPath: string): string[] { + const normalized = dirPath === '/' ? '' : (dirPath.startsWith('/') ? dirPath.slice(1) : dirPath); + const prefix = normalized ? normalized + '/' : ''; + const results = new Set(); + + for (const filePath of this.files.keys()) { + if (filePath.startsWith(prefix)) { + const relative = filePath.slice(prefix.length); + const firstPart = relative.split('/')[0]; + if (firstPart) { + results.add(firstPart); + } + } + } + + return Array.from(results); + } + + /** + * Get file/directory stats + */ + stat(path: string) { + const normalized = path.startsWith('/') ? path.slice(1) : path; + + // Check if it's a file + const data = this.files.get(normalized); + if (data) { + return { + type: 'file' as const, + mode: 0o100644, + size: data.length, + mtimeMs: Date.now(), + ino: 0, + uid: 0, + gid: 0 + }; + } + + // Check if it's a directory (has children) + const prefix = normalized ? normalized + '/' : ''; + for (const filePath of this.files.keys()) { + if (filePath.startsWith(prefix)) { + return { + type: 'dir' as const, + mode: 0o040755, + size: 0, + mtimeMs: Date.now(), + ino: 0, + uid: 0, + gid: 0 + }; + } + } + + const error: NodeJS.ErrnoException = new Error(`ENOENT: no such file or directory, stat '${path}'`); + error.code = 'ENOENT'; + throw error; + } + + /** + * Lstat (same as stat for in-memory fs) + */ + lstat(path: string) { + return this.stat(path); + } + + /** + * Create directory (no-op for in-memory fs) + */ + mkdir(): void { + // No-op: directories are implicit in path structure + } + + /** + * Remove directory (no-op for in-memory fs) + */ + rmdir(): void { + // No-op + } + + /** + * Delete file + */ + unlink(path: string): void { + const normalized = path.startsWith('/') ? path.slice(1) : path; + this.files.delete(normalized); + } + + /** + * Check if path exists + */ + exists(path: string): boolean { + try { + this.stat(path); + return true; + } catch { + return false; + } + } +} diff --git a/worker/agents/services/implementations/CodingAgent.ts b/worker/agents/services/implementations/CodingAgent.ts index 978f2835..9040f211 100644 --- a/worker/agents/services/implementations/CodingAgent.ts +++ b/worker/agents/services/implementations/CodingAgent.ts @@ -112,8 +112,16 @@ export class CodingAgentInterface { issue: string, toolRenderer: RenderToolCall, streamCb: (chunk: string) => void, - focusPaths?: string[], + focusPaths?: string[] ): Promise { return this.agentStub.executeDeepDebug(issue, toolRenderer, streamCb, focusPaths); } + + handleGitInfoRefs(): Promise { + return this.agentStub.handleGitInfoRefs(); + } + + handleGitUploadPack(): Promise { + return this.agentStub.handleGitUploadPack(); + } } diff --git a/worker/agents/services/interfaces/ICodingAgent.ts b/worker/agents/services/interfaces/ICodingAgent.ts index e71a331b..f34564da 100644 --- a/worker/agents/services/interfaces/ICodingAgent.ts +++ b/worker/agents/services/interfaces/ICodingAgent.ts @@ -59,4 +59,8 @@ export abstract class ICodingAgent { streamCb: (chunk: string) => void, focusPaths?: string[], ): Promise; + + // Git clone protocol handlers + abstract handleGitInfoRefs(): Promise; + abstract handleGitUploadPack(): Promise; } diff --git a/worker/api/controllers/appView/controller.ts b/worker/api/controllers/appView/controller.ts index c42cbdea..1dccca51 100644 --- a/worker/api/controllers/appView/controller.ts +++ b/worker/api/controllers/appView/controller.ts @@ -6,11 +6,13 @@ import { getAgentStub } from '../../../agents'; import { AppService } from '../../../database/services/AppService'; import { AppDetailsData, - AppStarToggleData, + AppStarToggleData, + GitCloneTokenData, } from './types'; import { AgentSummary } from '../../../agents/core/types'; import { createLogger } from '../../../logger'; import { buildUserWorkerUrl } from 'worker/utils/urls'; +import { JWTUtils } from '../../../utils/jwtUtils'; export class AppViewController extends BaseController { static logger = createLogger('AppViewController'); @@ -159,4 +161,185 @@ export class AppViewController extends BaseController { // return AppViewController.createErrorResponse('Internal server error', 500); // } // } + + /** + * Generate short-lived token for git clone (private repos only) + * POST /api/apps/:id/git/token + */ + static async generateGitCloneToken( + _request: Request, + env: Env, + _ctx: ExecutionContext, + context: RouteContext + ): Promise>> { + try { + const user = context.user!; + const appId = context.pathParams.id; + + if (!appId) { + return AppViewController.createErrorResponse('App ID is required', 400); + } + + // Generate short-lived JWT (1 hour) + const jwtUtils = JWTUtils.getInstance(env); + const expiresIn = 3600; // 1 hour + const token = await jwtUtils.createToken({ + sub: user.id, + email: user.email, + type: 'access' as const, + sessionId: 'git-clone-' + appId, // Special session for git operations + }, expiresIn); + + const responseData: GitCloneTokenData = { + token, + expiresIn, + expiresAt: new Date(Date.now() + expiresIn * 1000).toISOString(), + cloneUrl: `https://${token}@${env.CUSTOM_DOMAIN}/app/${appId}.git` + }; + + return AppViewController.createSuccessResponse(responseData); + } catch (error) { + this.logger.error('Error generating git clone token:', error); + return AppViewController.createErrorResponse('Failed to generate token', 500); + } + } + + /** + * Handle git info/refs request + * GET /app/:id.git/info/refs?service=git-upload-pack + */ + static async handleGitInfoRefs( + request: Request, + env: Env, + _ctx: ExecutionContext, + context: RouteContext + ): Promise { + try { + const appId = context.pathParams.id; + if (!appId) { + return new Response('App ID is required', { status: 400 }); + } + + // Check access + const hasAccess = await this.verifyGitAccess(request, env, appId); + if (!hasAccess) { + return new Response('Repository not found', { status: 404 }); + } + + // Get agent and handle git info/refs + const agentStub = await getAgentStub(env, appId, true, this.logger); + if (!agentStub || !(await agentStub.isInitialized())) { + return new Response('Repository not found', { status: 404 }); + } + + // Agent handles everything internally + const response = await agentStub.handleGitInfoRefs(); + + return new Response(response, { + status: 200, + headers: { + 'Content-Type': 'application/x-git-upload-pack-advertisement', + 'Cache-Control': 'no-cache' + } + }); + } catch (error) { + this.logger.error('Error handling git info/refs:', error); + return new Response('Internal server error', { status: 500 }); + } + } + + /** + * Handle git upload-pack request (actual clone) + * POST /app/:id.git/git-upload-pack + */ + static async handleGitUploadPack( + request: Request, + env: Env, + _ctx: ExecutionContext, + context: RouteContext + ): Promise { + try { + const appId = context.pathParams.id; + if (!appId) { + return new Response('App ID is required', { status: 400 }); + } + + // Check access + const hasAccess = await this.verifyGitAccess(request, env, appId); + if (!hasAccess) { + return new Response('Repository not found', { status: 404 }); + } + + // Get agent and handle git upload-pack + const agentStub = await getAgentStub(env, appId, true, this.logger); + if (!agentStub || !(await agentStub.isInitialized())) { + return new Response('Repository not found', { status: 404 }); + } + + // Agent handles everything internally + const packfile = await agentStub.handleGitUploadPack(); + + return new Response(packfile, { + status: 200, + headers: { + 'Content-Type': 'application/x-git-upload-pack-result', + 'Cache-Control': 'no-cache' + } + }); + } catch (error) { + this.logger.error('Error handling git upload-pack:', error); + return new Response('Internal server error', { status: 500 }); + } + } + + /** + * Verify git access (public apps or owner with valid token) + */ + private static async verifyGitAccess( + request: Request, + env: Env, + appId: string + ): Promise { + // Get app using app service + const appService = new AppService(env); + const app = await appService.getAppDetails(appId); + + if (!app) { + return false; + } + + // Public apps: anyone can clone + if (app.visibility === 'public') { + return true; + } + + // Private apps: require authentication + // Check for token in Authorization header or URL + const authHeader = request.headers.get('Authorization'); + let token: string | null = null; + + if (authHeader?.startsWith('Bearer ')) { + token = authHeader.slice(7); + } else if (authHeader?.startsWith('Basic ')) { + // Git sends credentials as Basic auth + const decoded = atob(authHeader.slice(6)); + const [username, password] = decoded.split(':'); + token = password || username; // Token might be in either field + } + + if (!token) { + return false; + } + + // Verify token using JWTUtils + const jwtUtils = JWTUtils.getInstance(env); + const payload = await jwtUtils.verifyToken(token); + + if (!payload) { + return false; + } + + // Check if user owns the app + return payload.sub === app.userId; + } } \ No newline at end of file diff --git a/worker/api/controllers/appView/types.ts b/worker/api/controllers/appView/types.ts index ec2455c6..e9acc6f1 100644 --- a/worker/api/controllers/appView/types.ts +++ b/worker/api/controllers/appView/types.ts @@ -38,6 +38,16 @@ export interface AppStarToggleData { starCount: number; } +/** + * Response data for git clone token generation + */ +export interface GitCloneTokenData { + token: string; + expiresIn: number; + expiresAt: string; + cloneUrl: string; +} + // /** // * Response data for forkApp // */ diff --git a/worker/api/routes/appRoutes.ts b/worker/api/routes/appRoutes.ts index f8fe734d..6c7eb4b9 100644 --- a/worker/api/routes/appRoutes.ts +++ b/worker/api/routes/appRoutes.ts @@ -65,6 +65,18 @@ export function setupAppRoutes(app: Hono): void { // Delete app - OWNER ONLY appRouter.delete('/:id', setAuthLevel(AuthConfig.ownerOnly), adaptController(AppController, AppController.deleteApp)); + // ======================================== + // GIT CLONE ROUTES + // ======================================== + + // Generate git clone token for private repos - OWNER ONLY + appRouter.post('/:id/git/token', setAuthLevel(AuthConfig.ownerOnly), adaptController(AppViewController, AppViewController.generateGitCloneToken)); + + // Git protocol endpoints (public with internal access check) + // Note: These return raw Response (not JSON), but adaptController handles that fine + appRouter.get('/:id.git/info/refs', setAuthLevel(AuthConfig.public), adaptController(AppViewController, AppViewController.handleGitInfoRefs)); + appRouter.post('/:id.git/git-upload-pack', setAuthLevel(AuthConfig.public), adaptController(AppViewController, AppViewController.handleGitUploadPack)); + // Mount the app router under /api/apps app.route('/api/apps', appRouter); } From 5bff5235f941fa198d93b482b74afcce64a4b716 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 24 Oct 2025 16:32:56 -0400 Subject: [PATCH 074/150] fix: add ESM type declarations for isomorphic-git and update imports --- tsconfig.worker.json | 2 +- worker/agents/git/git-clone-service.ts | 2 +- worker/agents/git/git.ts | 2 +- worker/types/isomorphic-git.d.ts | 9 +++++++++ 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 worker/types/isomorphic-git.d.ts diff --git a/tsconfig.worker.json b/tsconfig.worker.json index 9ee760f5..e01b4811 100644 --- a/tsconfig.worker.json +++ b/tsconfig.worker.json @@ -6,5 +6,5 @@ "types": ["./worker-configuration.d.ts", "vite/client", "jest"], "lib": ["ES2023"] }, - "include": ["./worker-configuration.d.ts", "./shared", "./worker"] + "include": ["./worker-configuration.d.ts", "./shared", "./worker", "./worker/types"] } diff --git a/worker/agents/git/git-clone-service.ts b/worker/agents/git/git-clone-service.ts index 3f7e0f1c..9b08b38e 100644 --- a/worker/agents/git/git-clone-service.ts +++ b/worker/agents/git/git-clone-service.ts @@ -3,7 +3,7 @@ * Handles template rebasing and git HTTP protocol */ -import git from 'isomorphic-git'; +import git from 'isomorphic-git/index.js'; import { MemFS } from './memfs'; import { SqliteFS } from './fs-adapter'; import { createLogger } from '../../logger'; diff --git a/worker/agents/git/git.ts b/worker/agents/git/git.ts index 605a8c56..cdcd3c80 100644 --- a/worker/agents/git/git.ts +++ b/worker/agents/git/git.ts @@ -2,7 +2,7 @@ * Git version control for Durable Objects using isomorphic-git */ -import git from 'isomorphic-git'; +import git from 'isomorphic-git/index.js'; import { SqliteFS, type SqlExecutor } from './fs-adapter'; import { FileOutputType } from '../schemas'; diff --git a/worker/types/isomorphic-git.d.ts b/worker/types/isomorphic-git.d.ts new file mode 100644 index 00000000..d3719896 --- /dev/null +++ b/worker/types/isomorphic-git.d.ts @@ -0,0 +1,9 @@ +/** + * Type declarations for isomorphic-git ESM build + * Fixes "Cannot find module 'isomorphic-git/index.js'" error + */ +declare module 'isomorphic-git/index.js' { + export * from 'isomorphic-git'; + import git from 'isomorphic-git'; + export default git; +} From 9e1a98bb5dcb170b7ccba2c6430dbfeaeea7405d Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Fri, 24 Oct 2025 16:36:45 -0400 Subject: [PATCH 075/150] fix: standardize isomorphic-git imports and resolve ESM compatibility issues --- vite.config.ts | 2 ++ worker/agents/git/git-clone-service.ts | 2 +- worker/agents/git/git.ts | 2 +- worker/types/isomorphic-git.d.ts | 9 --------- 4 files changed, 4 insertions(+), 11 deletions(-) delete mode 100644 worker/types/isomorphic-git.d.ts diff --git a/vite.config.ts b/vite.config.ts index 239086d0..7f7fb1d2 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -53,6 +53,8 @@ export default defineConfig({ // 'path': 'path-browserify', // Add this line to fix the 'debug' package issue debug: 'debug/src/browser', + // Force isomorphic-git to use ESM build (not CommonJS) + 'isomorphic-git': path.resolve(__dirname, './node_modules/isomorphic-git/index.js'), // "@": path.resolve(__dirname, "./src"), '@': path.resolve(__dirname, './src'), 'shared': path.resolve(__dirname, './shared'), diff --git a/worker/agents/git/git-clone-service.ts b/worker/agents/git/git-clone-service.ts index 9b08b38e..3f7e0f1c 100644 --- a/worker/agents/git/git-clone-service.ts +++ b/worker/agents/git/git-clone-service.ts @@ -3,7 +3,7 @@ * Handles template rebasing and git HTTP protocol */ -import git from 'isomorphic-git/index.js'; +import git from 'isomorphic-git'; import { MemFS } from './memfs'; import { SqliteFS } from './fs-adapter'; import { createLogger } from '../../logger'; diff --git a/worker/agents/git/git.ts b/worker/agents/git/git.ts index cdcd3c80..605a8c56 100644 --- a/worker/agents/git/git.ts +++ b/worker/agents/git/git.ts @@ -2,7 +2,7 @@ * Git version control for Durable Objects using isomorphic-git */ -import git from 'isomorphic-git/index.js'; +import git from 'isomorphic-git'; import { SqliteFS, type SqlExecutor } from './fs-adapter'; import { FileOutputType } from '../schemas'; diff --git a/worker/types/isomorphic-git.d.ts b/worker/types/isomorphic-git.d.ts deleted file mode 100644 index d3719896..00000000 --- a/worker/types/isomorphic-git.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Type declarations for isomorphic-git ESM build - * Fixes "Cannot find module 'isomorphic-git/index.js'" error - */ -declare module 'isomorphic-git/index.js' { - export * from 'isomorphic-git'; - import git from 'isomorphic-git'; - export default git; -} From 27640b8020a767cd3e2ccbe0282c8cc961284408 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sat, 25 Oct 2025 12:22:36 -0400 Subject: [PATCH 076/150] feat: use custom isomorphic-git fork + fix fs adapter and integration --- bun.lock | 8 +- package.json | 2 +- vite.config.ts | 27 +- worker/agents/core/simpleGeneratorAgent.ts | 93 +++-- worker/agents/git/fs-adapter.ts | 329 +++++++++++++++--- worker/agents/git/git-clone-service.ts | 8 +- worker/agents/git/git.ts | 53 ++- .../services/implementations/CodingAgent.ts | 8 - .../services/implementations/FileManager.ts | 13 +- .../services/interfaces/ICodingAgent.ts | 4 - .../services/interfaces/IFileManager.ts | 6 +- worker/api/controllers/appView/controller.ts | 13 +- worker/api/handlers/git-protocol.ts | 109 ++++++ worker/api/routes/appRoutes.ts | 4 - worker/index.ts | 9 +- 15 files changed, 539 insertions(+), 147 deletions(-) create mode 100644 worker/api/handlers/git-protocol.ts diff --git a/bun.lock b/bun.lock index e925b322..84f7c027 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,7 @@ "": { "name": "vibesdk", "dependencies": { + "@ashishkumar472/cf-git": "1.0.3", "@cloudflare/containers": "^0.0.28", "@cloudflare/sandbox": "0.4.7", "@noble/ciphers": "^1.3.0", @@ -62,7 +63,6 @@ "hono": "^4.9.9", "html2canvas-pro": "^1.5.11", "input-otp": "^1.4.2", - "isomorphic-git": "^1.34.0", "jose": "^5.10.0", "jsonc-parser": "^3.3.1", "latest": "^0.2.0", @@ -142,6 +142,8 @@ "@apidevtools/json-schema-ref-parser": ["@apidevtools/json-schema-ref-parser@11.9.3", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" } }, "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ=="], + "@ashishkumar472/cf-git": ["@ashishkumar472/cf-git@1.0.3", "", { "dependencies": { "clean-git-ref": "^2.0.1", "crc-32": "^1.2.0", "diff3": "0.0.3", "ignore": "^5.1.4", "is-git-ref-name-valid": "^1.0.0", "minimisted": "^2.0.0", "pako": "^1.0.10", "pify": "^4.0.1", "readable-stream": "^3.4.0", "simple-get": "^4.0.1" }, "bin": { "isogit": "cli.cjs" } }, "sha512-f6/r6OhZf31Wk8zm81ikmMKn+D4+EsacH6ym+WlE+GG2j5UTMDupuqvRaZMKWKkSYFqd6O5G19ROy+muUZLabA=="], + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], "@babel/compat-data": ["@babel/compat-data@7.28.4", "", {}, "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw=="], @@ -1020,8 +1022,6 @@ "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], - "async-lock": ["async-lock@1.4.1", "", {}, "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ=="], - "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], @@ -1696,8 +1696,6 @@ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - "isomorphic-git": ["isomorphic-git@1.34.0", "", { "dependencies": { "async-lock": "^1.4.1", "clean-git-ref": "^2.0.1", "crc-32": "^1.2.0", "diff3": "0.0.3", "ignore": "^5.1.4", "is-git-ref-name-valid": "^1.0.0", "minimisted": "^2.0.0", "pako": "^1.0.10", "path-browserify": "^1.0.1", "pify": "^4.0.1", "readable-stream": "^3.4.0", "sha.js": "^2.4.12", "simple-get": "^4.0.1" }, "bin": { "isogit": "cli.cjs" } }, "sha512-J82yRa/4wm9VuOWSlI37I9Sa+n1gWaSWuKQk8zhpo6RqTW+ZTcK5c/KubLMcuVU3Btc+maRCa3YlRKqqY9q7qQ=="], - "isomorphic-timers-promises": ["isomorphic-timers-promises@1.0.1", "", {}, "sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ=="], "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], diff --git a/package.json b/package.json index d6fb6d04..f49dfafa 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "knip:exports": "knip --exports" }, "dependencies": { + "@ashishkumar472/cf-git": "1.0.3", "@cloudflare/containers": "^0.0.28", "@cloudflare/sandbox": "0.4.7", "@noble/ciphers": "^1.3.0", @@ -90,7 +91,6 @@ "hono": "^4.9.9", "html2canvas-pro": "^1.5.11", "input-otp": "^1.4.2", - "isomorphic-git": "^1.34.0", "jose": "^5.10.0", "jsonc-parser": "^3.3.1", "latest": "^0.2.0", diff --git a/vite.config.ts b/vite.config.ts index 7f7fb1d2..31b98a60 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,14 +6,13 @@ import path from 'path'; import { cloudflare } from '@cloudflare/vite-plugin'; import tailwindcss from '@tailwindcss/vite'; -// import { nodePolyfills } from 'vite-plugin-node-polyfills'; // https://vite.dev/config/ export default defineConfig({ optimizeDeps: { exclude: ['format', 'editor.all'], include: ['monaco-editor/esm/vs/editor/editor.api'], - force: true, // Force re-optimization on every start + force: true, }, // build: { @@ -31,16 +30,7 @@ export default defineConfig({ cloudflare({ configPath: 'wrangler.jsonc', experimental: { remoteBindings: true }, - }), // Add the node polyfills plugin here - // nodePolyfills({ - // exclude: [ - // 'tty', // Exclude 'tty' module - // ], - // // We recommend leaving this as `true` to polyfill `global`. - // globals: { - // global: true, - // }, - // }) + }), tailwindcss(), // sentryVitePlugin({ // org: 'cloudflare-0u', @@ -50,15 +40,10 @@ export default defineConfig({ resolve: { alias: { - // 'path': 'path-browserify', - // Add this line to fix the 'debug' package issue debug: 'debug/src/browser', - // Force isomorphic-git to use ESM build (not CommonJS) - 'isomorphic-git': path.resolve(__dirname, './node_modules/isomorphic-git/index.js'), - // "@": path.resolve(__dirname, "./src"), '@': path.resolve(__dirname, './src'), - 'shared': path.resolve(__dirname, './shared'), - 'worker': path.resolve(__dirname, './worker'), + 'shared': path.resolve(__dirname, './shared'), + 'worker': path.resolve(__dirname, './worker'), }, }, @@ -84,8 +69,4 @@ export default defineConfig({ // Clear cache more aggressively cacheDir: 'node_modules/.vite', - - build: { - sourcemap: true, - }, }); diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index d74fce9a..0a1e735c 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -34,7 +34,7 @@ import { InferenceContext, AgentActionKey } from '../inferutils/config.types'; import { AGENT_CONFIG } from '../inferutils/config'; import { ModelConfigService } from '../../database/services/ModelConfigService'; import { fixProjectIssues } from '../../services/code-fixer'; -import { GitVersionControl, GitCloneService } from '../git'; +import { GitVersionControl } from '../git'; import { FastCodeFixerOperation } from '../operations/PostPhaseCodeFixer'; import { looksLikeCommand } from '../utils/common'; import { generateBlueprint } from '../planning/blueprint'; @@ -49,6 +49,7 @@ import { ConversationMessage, ConversationState } from '../inferutils/common'; import { DeepCodeDebugger } from '../assistants/codeDebugger'; import { DeepDebugResult } from './types'; import { StateMigration } from './stateMigration'; +import { GitCloneService } from '../git/git-clone-service'; interface Operations { codeReview: CodeReviewOperation; @@ -162,8 +163,8 @@ export class SimpleCodeGeneratorAgent extends Agent { (s) => this.setState(s) ); - // Initialize GitVersionControl - this.git = new GitVersionControl(this.sql); + // Initialize GitVersionControl (bind sql to preserve 'this' context) + this.git = new GitVersionControl(this.sql.bind(this)); // Initialize FileManager this.fileManager = new FileManager(this.stateManager, () => this.getTemplateDetails(), this.git); @@ -560,7 +561,7 @@ export class SimpleCodeGeneratorAgent extends Agent { const readme = await this.operations.implementPhase.generateReadme(this.getOperationOptions()); - this.fileManager.saveGeneratedFile(readme, "feat: README.md"); + await this.fileManager.saveGeneratedFile(readme, "feat: README.md"); this.broadcast(WebSocketMessageResponses.FILE_GENERATED, { message: 'README.md generated successfully', @@ -1144,7 +1145,7 @@ export class SimpleCodeGeneratorAgent extends Agent { }); // Update state with completed phase - this.fileManager.saveGeneratedFiles(finalFiles, `feat: ${phase.name}`); + await this.fileManager.saveGeneratedFiles(finalFiles, `feat: ${phase.name}`); this.logger().info("Files generated for phase:", phase.name, finalFiles.map(f => f.filePath)); @@ -1309,7 +1310,7 @@ export class SimpleCodeGeneratorAgent extends Agent { this.getOperationOptions() ); - const fileState = this.fileManager.saveGeneratedFile(result, `fix: ${file.filePath}`); + const fileState = await this.fileManager.saveGeneratedFile(result, `fix: ${file.filePath}`); this.broadcast(WebSocketMessageResponses.FILE_REGENERATED, { message: `Regenerated file: ${file.filePath}`, @@ -1420,7 +1421,7 @@ export class SimpleCodeGeneratorAgent extends Agent { }, this.getOperationOptions()); if (fastCodeFixer.length > 0) { - this.fileManager.saveGeneratedFiles(fastCodeFixer, "fix: Fast smart code fixes"); + await this.fileManager.saveGeneratedFiles(fastCodeFixer, "fix: Fast smart code fixes"); await this.deployToSandbox(fastCodeFixer); this.logger().info("Fast smart code fixes applied successfully"); } @@ -1492,7 +1493,7 @@ export class SimpleCodeGeneratorAgent extends Agent { filePurpose: allFiles.find(f => f.filePath === file.filePath)?.filePurpose || '', fileContents: file.fileContents })); - this.fileManager.saveGeneratedFiles(fixedFiles, "fix: applied deterministic fixes"); + await this.fileManager.saveGeneratedFiles(fixedFiles, "fix: applied deterministic fixes"); await this.deployToSandbox(fixedFiles, false, "fix: applied deterministic fixes"); this.logger().info("Deployed deterministic fixes to sandbox"); @@ -2023,7 +2024,7 @@ export class SimpleCodeGeneratorAgent extends Agent { '[cloudflarebutton]', prepareCloudflareButton(options.repositoryHtmlUrl, 'markdown') ); - this.fileManager.saveGeneratedFile(readmeFile, "feat: README updated with Cloudflare deploy button"); + await this.fileManager.saveGeneratedFile(readmeFile, "feat: README updated with Cloudflare deploy button"); this.logger().info('README prepared with Cloudflare deploy button'); // Deploy updated README to sandbox so it's visible in preview @@ -2346,13 +2347,48 @@ export class SimpleCodeGeneratorAgent extends Agent { * Returns git protocol advertisement */ async handleGitInfoRefs(): Promise { - const fs = await GitCloneService.buildRepository({ - agentGitFS: this.git.fs, - templateDetails: this.templateDetailsCache, - appQuery: this.state.query - }); - - return await GitCloneService.handleInfoRefs(fs); + try { + console.log('[Agent] handleGitInfoRefs called'); + + // Ensure git has at least one commit before allowing clone + const head = await this.git.getHead(); + console.log('[Agent] git.getHead() returned:', head); + if (!head) { + console.log('[Agent] No HEAD found, creating initial commit from current files'); + + // Get all current files from fileManager + const allFiles = this.fileManager.getAllFiles(); + + if (allFiles.length > 0) { + // Create initial commit with current files + const files = allFiles.map(f => ({ + filePath: f.filePath, + fileContents: f.fileContents + })); + await this.git.commit(files, `Initial commit\n\nGenerated by Vibesdk\nQuery: ${this.state.query || 'N/A'}`); + console.log('[Agent] Initial commit created with', files.length, 'files'); + } else { + console.log('[Agent] No files to commit, returning empty repo'); + // Return empty advertisement for truly empty repos + return '001e# service=git-upload-pack\n0000'; + } + } + + // Build repository with template rebasing + const repoFS = await GitCloneService.buildRepository({ + agentGitFS: this.git.fs, + templateDetails: this.templateDetailsCache || null, + appQuery: this.state.query || 'N/A' + }); + + // Use GitCloneService to handle info/refs + const result = await GitCloneService.handleInfoRefs(repoFS); + console.log('[Agent] handleGitInfoRefs completed, response length:', result.length); + return result; + } catch (error) { + console.error('[Agent] handleGitInfoRefs failed:', error); + throw error; + } } /** @@ -2360,12 +2396,23 @@ export class SimpleCodeGeneratorAgent extends Agent { * Returns packfile for git clone */ async handleGitUploadPack(): Promise { - const fs = await GitCloneService.buildRepository({ - agentGitFS: this.git.fs, - templateDetails: this.templateDetailsCache, - appQuery: this.state.query - }); - - return await GitCloneService.handleUploadPack(fs); + try { + console.log('[Agent] handleGitUploadPack called'); + + // Build repository with template rebasing + const repoFS = await GitCloneService.buildRepository({ + agentGitFS: this.git.fs, + templateDetails: this.templateDetailsCache || null, + appQuery: this.state.query || 'N/A' + }); + + // Use GitCloneService to handle upload-pack + const packfile = await GitCloneService.handleUploadPack(repoFS); + console.log('[Agent] handleGitUploadPack completed, packfile size:', packfile.length); + return packfile; + } catch (error) { + console.error('[Agent] handleGitUploadPack failed:', error); + throw error; + } } } diff --git a/worker/agents/git/fs-adapter.ts b/worker/agents/git/fs-adapter.ts index b4441219..5cc65c83 100644 --- a/worker/agents/git/fs-adapter.ts +++ b/worker/agents/git/fs-adapter.ts @@ -16,13 +16,18 @@ export interface SqlExecutor { const MAX_OBJECT_SIZE = 900 * 1024; // 900KB export class SqliteFS { - constructor(private sql: SqlExecutor) {} + private sql!: SqlExecutor; // Assigned in constructor + public promises!: this; // Set in init(), required by isomorphic-git + + constructor(sql: SqlExecutor) { + this.sql = sql; + } /** * Get storage statistics for observability */ getStorageStats(): { totalObjects: number; totalBytes: number; largestObject: { path: string; size: number } | null } { - const objects = this.sql<{ path: string; data: string }>`SELECT path, data FROM git_objects`; + const objects = this.sql<{ path: string; data: string; is_dir: number }>`SELECT path, data, is_dir FROM git_objects WHERE is_dir = 0`; if (!objects || objects.length === 0) { return { totalObjects: 0, totalBytes: 0, largestObject: null }; @@ -47,40 +52,83 @@ export class SqliteFS { }; } - init(): void { + init() { + // Create table this.sql` CREATE TABLE IF NOT EXISTS git_objects ( path TEXT PRIMARY KEY, data TEXT NOT NULL, + is_dir INTEGER NOT NULL DEFAULT 0, mtime INTEGER NOT NULL ) `; - // Create index for efficient directory listings + // Create indexes for efficient lookups this.sql`CREATE INDEX IF NOT EXISTS idx_git_objects_path ON git_objects(path)`; + this.sql`CREATE INDEX IF NOT EXISTS idx_git_objects_is_dir ON git_objects(is_dir, path)`; + + // Ensure root directory exists + this.sql`INSERT OR IGNORE INTO git_objects (path, data, is_dir, mtime) VALUES ('', '', 1, ${Date.now()})`; + + // Make promises property enumerable for isomorphic-git FileSystem detection + Object.defineProperty(this, 'promises', { + value: this, + enumerable: true, + writable: false, + configurable: false + }); } - readFile(path: string, options?: { encoding?: 'utf8' }): Uint8Array | string { + async readFile(path: string, options?: { encoding?: 'utf8' }): Promise { // Normalize path (remove leading slashes) const normalized = path.replace(/^\/+/, ''); - const result = this.sql<{ data: string }>`SELECT data FROM git_objects WHERE path = ${normalized}`; - if (!result[0]) throw new Error(`ENOENT: ${path}`); + console.log(`[Git FS] readFile: ${normalized} (encoding: ${options?.encoding || 'binary'}) - START`); + const result = this.sql<{ data: string; is_dir: number }>`SELECT data, is_dir FROM git_objects WHERE path = ${normalized}`; + if (!result[0]) { + console.log(`[Git FS] readFile: ${normalized} - ENOENT`); + const error: NodeJS.ErrnoException = new Error(`ENOENT: no such file or directory, open '${path}'`); + error.code = 'ENOENT'; + error.errno = -2; + error.path = path; + throw error; + } + + // Check if it's a directory (directories can't be read as files) + if (result[0].is_dir) { + const error: NodeJS.ErrnoException = new Error(`EISDIR: illegal operation on a directory, read '${path}'`); + error.code = 'EISDIR'; + error.errno = -21; + error.path = path; + throw error; + } const base64Data = result[0].data; - // Decode from base64 + // Decode from base64 - handle empty files + if (!base64Data) { + console.log(`[Git FS] readFile: ${normalized} -> empty file`); + return options?.encoding === 'utf8' ? '' : new Uint8Array(0); + } + const binaryString = atob(base64Data); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } - return options?.encoding === 'utf8' ? new TextDecoder().decode(bytes) : bytes; + const fileContent = options?.encoding === 'utf8' ? new TextDecoder().decode(bytes) : bytes; + console.log(`[Git FS] readFile: ${normalized} -> ${bytes.length} bytes - COMPLETE`); + return fileContent; } - writeFile(path: string, data: Uint8Array | string): void { + async writeFile(path: string, data: Uint8Array | string): Promise { // Normalize path (remove leading slashes) const normalized = path.replace(/^\/+/, ''); + console.log(`[Git FS] writeFile: ${normalized} - START`); + + if (!normalized) { + throw new Error('Cannot write to root'); + } // Convert to Uint8Array if string const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data; @@ -90,83 +138,274 @@ export class SqliteFS { throw new Error(`File too large: ${path} (${bytes.length} bytes, max ${MAX_OBJECT_SIZE})`); } - // Encode to base64 for safe storage - let binaryString = ''; - for (let i = 0; i < bytes.length; i++) { - binaryString += String.fromCharCode(bytes[i]); + // Check if path exists as directory + const existing = this.sql<{ is_dir: number }>`SELECT is_dir FROM git_objects WHERE path = ${normalized}`; + if (existing[0]?.is_dir === 1) { + const error: NodeJS.ErrnoException = new Error(`EISDIR: illegal operation on a directory, open '${path}'`); + error.code = 'EISDIR'; + error.errno = -21; + error.path = path; + throw error; } - const base64Content = btoa(binaryString); - - this.sql`INSERT OR REPLACE INTO git_objects (path, data, mtime) VALUES (${normalized}, ${base64Content}, ${Date.now()})`; - // Only log if approaching size limit (no overhead for normal files) - if (bytes.length > MAX_OBJECT_SIZE * 0.8) { - console.warn(`[Git Storage] Large file: ${normalized} is ${(bytes.length / 1024).toFixed(1)}KB (limit: ${(MAX_OBJECT_SIZE / 1024).toFixed(1)}KB)`); + // Ensure parent directories exist (git implicitly creates them) + const parts = normalized.split('/'); + if (parts.length > 1) { + const now = Date.now(); + for (let i = 0; i < parts.length - 1; i++) { + const dirPath = parts.slice(0, i + 1).join('/'); + this.sql`INSERT OR IGNORE INTO git_objects (path, data, is_dir, mtime) VALUES (${dirPath}, '', 1, ${now})`; + } } + + // Encode to base64 for safe storage (handle empty files) + const base64Content = bytes.length === 0 ? '' : btoa(String.fromCharCode(...Array.from(bytes))); + + this.sql`INSERT OR REPLACE INTO git_objects (path, data, is_dir, mtime) VALUES (${normalized}, ${base64Content}, 0, ${Date.now()})`; + console.log(`[Git FS] writeFile: ${normalized} -> ${bytes.length} bytes written - COMPLETE`); } - unlink(path: string): void { + async unlink(path: string): Promise { // Normalize path (remove leading slashes) const normalized = path.replace(/^\/+/, ''); - this.sql`DELETE FROM git_objects WHERE path = ${normalized}`; + console.log(`[Git FS] unlink: ${normalized} - START`); + + // Check if exists and is not a directory + const existing = this.sql<{ is_dir: number }>`SELECT is_dir FROM git_objects WHERE path = ${normalized}`; + if (!existing[0]) { + const error: NodeJS.ErrnoException = new Error(`ENOENT: no such file or directory, unlink '${path}'`); + error.code = 'ENOENT'; + error.errno = -2; + error.path = path; + throw error; + } + if (existing[0].is_dir === 1) { + const error: NodeJS.ErrnoException = new Error(`EPERM: operation not permitted, unlink '${path}'`); + error.code = 'EPERM'; + error.errno = -1; + error.path = path; + throw error; + } + + this.sql`DELETE FROM git_objects WHERE path = ${normalized} AND is_dir = 0`; + console.log(`[Git FS] unlink: ${normalized} -> deleted - COMPLETE`); } - readdir(path: string): string[] { + async readdir(path: string): Promise { // Normalize path (remove leading/trailing slashes) const normalized = path.replace(/^\/+|\/+$/g, ''); + console.log(`[Git FS] readdir: ${normalized} - START`); + + // Check if directory exists + const dirCheck = this.sql<{ is_dir: number }>`SELECT is_dir FROM git_objects WHERE path = ${normalized}`; + if (!dirCheck[0] || !dirCheck[0].is_dir) { + const error: NodeJS.ErrnoException = new Error(`ENOENT: no such file or directory, scandir '${path}'`); + error.code = 'ENOENT'; + error.errno = -2; + error.path = path; + throw error; + } - let result; + let rows; if (normalized === '') { - // Root directory - get all paths - result = this.sql<{ path: string }>`SELECT path FROM git_objects`; + // Root directory - get all direct children + rows = this.sql<{ path: string }>`SELECT path FROM git_objects WHERE path != '' AND path NOT LIKE '%/%'`; } else { - // Subdirectory - match prefix - result = this.sql<{ path: string }>`SELECT path FROM git_objects WHERE path LIKE ${normalized + '/%'}`; + // Subdirectory - get direct children only + rows = this.sql<{ path: string }>`SELECT path FROM git_objects WHERE path LIKE ${normalized + '/%'}`; } - if (!result || result.length === 0) return []; + if (!rows || rows.length === 0) return []; const children = new Set(); const prefixLen = normalized ? normalized.length + 1 : 0; - for (const row of result) { + for (const row of rows) { const relativePath = normalized ? row.path.substring(prefixLen) : row.path; const first = relativePath.split('/')[0]; if (first) children.add(first); } - return Array.from(children); + const result = Array.from(children); + console.log(`[Git FS] readdir: ${normalized} -> [${result.join(', ')}] (${result.length} entries) - COMPLETE`); + return result; } - mkdir(_path: string): void { - // No-op: directories are implicit in Git + async mkdir(path: string, _options?: { recursive?: boolean }): Promise { + // Normalize path (remove leading/trailing slashes) + const normalized = path.replace(/^\/+|\/+$/g, ''); + + // Don't create root (already exists) + if (!normalized) return; + + console.log(`[Git FS] mkdir: ${normalized} - START`); + + // Quick check: if parent is root, we can skip parent validation + const parts = normalized.split('/'); + const isDirectChildOfRoot = parts.length === 1; + + if (!isDirectChildOfRoot) { + // Check parent exists first (avoid unnecessary queries) + const parentPath = parts.slice(0, -1).join('/'); + const parent = this.sql<{ is_dir: number }>`SELECT is_dir FROM git_objects WHERE path = ${parentPath}`; + if (!parent[0] || parent[0].is_dir !== 1) { + // Parent doesn't exist - throw ENOENT + // Isomorphic-git's FileSystem wrapper will catch this and recursively create parent + const error: NodeJS.ErrnoException = new Error(`ENOENT: no such file or directory, mkdir '${path}'`); + error.code = 'ENOENT'; + error.errno = -2; + error.path = path; + throw error; + } + } + + // Check if already exists (after parent check to fail fast on missing parent) + const existing = this.sql<{ is_dir: number }>`SELECT is_dir FROM git_objects WHERE path = ${normalized}`; + if (existing[0]) { + if (existing[0].is_dir === 1) { + // Already exists as directory - this is OK (idempotent) + console.log(`[Git FS] mkdir: ${normalized} already exists - COMPLETE`); + return; + } else { + // Exists as file - can't create directory + const error: NodeJS.ErrnoException = new Error(`EEXIST: file already exists, mkdir '${path}'`); + error.code = 'EEXIST'; + error.errno = -17; + error.path = path; + throw error; + } + } + + // Create directory entry + this.sql`INSERT OR IGNORE INTO git_objects (path, data, is_dir, mtime) VALUES (${normalized}, '', 1, ${Date.now()})`; + console.log(`[Git FS] mkdir: ${normalized} created - COMPLETE`); } - rmdir(path: string): void { + async rmdir(path: string): Promise { // Normalize path (remove leading/trailing slashes) const normalized = path.replace(/^\/+|\/+$/g, ''); - this.sql`DELETE FROM git_objects WHERE path LIKE ${normalized + '%'}`; + console.log(`[Git FS] rmdir: ${normalized} - START`); + + if (!normalized) { + throw new Error('Cannot remove root directory'); + } + + // Check if exists and is a directory + const existing = this.sql<{ is_dir: number }>`SELECT is_dir FROM git_objects WHERE path = ${normalized}`; + if (!existing[0]) { + const error: NodeJS.ErrnoException = new Error(`ENOENT: no such file or directory, rmdir '${path}'`); + error.code = 'ENOENT'; + error.errno = -2; + error.path = path; + throw error; + } + if (existing[0].is_dir !== 1) { + const error: NodeJS.ErrnoException = new Error(`ENOTDIR: not a directory, rmdir '${path}'`); + error.code = 'ENOTDIR'; + error.errno = -20; + error.path = path; + throw error; + } + + // Check if directory is empty (has no children) + const children = this.sql<{ path: string }>`SELECT path FROM git_objects WHERE path LIKE ${normalized + '/%'} LIMIT 1`; + if (children.length > 0) { + const error: NodeJS.ErrnoException = new Error(`ENOTEMPTY: directory not empty, rmdir '${path}'`); + error.code = 'ENOTEMPTY'; + error.errno = -39; + error.path = path; + throw error; + } + + // Remove the directory + this.sql`DELETE FROM git_objects WHERE path = ${normalized}`; + console.log(`[Git FS] rmdir: ${normalized} -> deleted - COMPLETE`); } - stat(path: string): { type: 'file' | 'dir'; mode: number; size: number; mtimeMs: number } { + async stat(path: string): Promise<{ type: 'file' | 'dir'; mode: number; size: number; mtimeMs: number }> { // Normalize path (remove leading slashes) const normalized = path.replace(/^\/+/, ''); - const result = this.sql<{ data: string; mtime: number }>`SELECT data, mtime FROM git_objects WHERE path = ${normalized}`; - if (!result[0]) throw new Error(`ENOENT: ${path}`); + console.log(`[Git FS] stat: ${normalized} - START`); + const result = this.sql<{ data: string; mtime: number; is_dir: number }>`SELECT data, mtime, is_dir FROM git_objects WHERE path = ${normalized}`; + if (!result[0]) { + const error: NodeJS.ErrnoException = new Error(`ENOENT: no such file or directory, stat '${path}'`); + error.code = 'ENOENT'; + error.errno = -2; + error.path = path; + throw error; + } const row = result[0]; - return { type: 'file', mode: 0o100644, size: row.data.length, mtimeMs: row.mtime }; + const isDir = row.is_dir === 1; + + // Calculate actual size for files (base64 is ~1.33x larger than binary) + let size = 0; + if (!isDir && row.data) { + // Approximate binary size from base64 length + size = Math.floor(row.data.length * 0.75); + } + + const statResult = { + type: (isDir ? 'dir' : 'file') as 'file' | 'dir', + mode: isDir ? 0o040755 : 0o100644, + size, + mtimeMs: row.mtime, + // Add full Node.js stat properties for isomorphic-git + dev: 0, + ino: 0, + uid: 0, + gid: 0, + ctime: new Date(row.mtime), + mtime: new Date(row.mtime), + ctimeMs: row.mtime, + // Add methods that isomorphic-git expects + isFile: () => !isDir, + isDirectory: () => isDir, + isSymbolicLink: () => false, // We don't support symlinks yet + }; + console.log(`[Git FS] stat: ${normalized} -> ${statResult.type} (${statResult.size} bytes) - COMPLETE`); + return statResult; + } + + async lstat(path: string) { + console.log(`[Git FS] lstat: ${path} (delegating to stat)`); + return await this.stat(path); + } + + async symlink(target: string, path: string): Promise { + console.log(`[Git FS] symlink: ${path} -> ${target}`); + await this.writeFile(path, target); } - lstat(path: string) { - return this.stat(path); + async readlink(path: string): Promise { + console.log(`[Git FS] readlink: ${path}`); + return (await this.readFile(path, { encoding: 'utf8' })) as string; } - symlink(target: string, path: string): void { - this.writeFile(path, target); + /** + * Check if a file or directory exists + * Required by isomorphic-git's init check + */ + async exists(path: string): Promise { + console.log(`[Git FS] exists: ${path}`); + try { + await this.stat(path); + console.log(`[Git FS] exists: ${path} -> true`); + return true; + } catch (err) { + if ((err as NodeJS.ErrnoException).code === 'ENOENT') { + console.log(`[Git FS] exists: ${path} -> false`); + return false; + } + throw err; + } } - readlink(path: string): string { - return this.readFile(path, { encoding: 'utf8' }) as string; + /** + * Alias for writeFile (isomorphic-git sometimes uses 'write') + */ + async write(path: string, data: Uint8Array | string): Promise { + console.log(`[Git FS] write: ${path}`); + return await this.writeFile(path, data); } } \ No newline at end of file diff --git a/worker/agents/git/git-clone-service.ts b/worker/agents/git/git-clone-service.ts index 3f7e0f1c..de4e7c15 100644 --- a/worker/agents/git/git-clone-service.ts +++ b/worker/agents/git/git-clone-service.ts @@ -3,7 +3,7 @@ * Handles template rebasing and git HTTP protocol */ -import git from 'isomorphic-git'; +import git from '@ashishkumar472/cf-git'; import { MemFS } from './memfs'; import { SqliteFS } from './fs-adapter'; import { createLogger } from '../../logger'; @@ -139,17 +139,17 @@ export class GitCloneService { targetFS: MemFS, dirPath: string ): Promise { - const entries = sourceFS.readdir(dirPath); + const entries = await sourceFS.readdir(dirPath); for (const entry of entries) { const fullPath = `${dirPath}/${entry}`; try { - const stat = sourceFS.stat(fullPath); + const stat = await sourceFS.stat(fullPath); if (stat.type === 'file') { // Copy file - const data = sourceFS.readFile(fullPath); + const data = await sourceFS.readFile(fullPath); targetFS.writeFile(fullPath, data as Uint8Array); } else if (stat.type === 'dir') { // Recursively copy directory diff --git a/worker/agents/git/git.ts b/worker/agents/git/git.ts index 605a8c56..0f89d974 100644 --- a/worker/agents/git/git.ts +++ b/worker/agents/git/git.ts @@ -2,7 +2,7 @@ * Git version control for Durable Objects using isomorphic-git */ -import git from 'isomorphic-git'; +import git from '@ashishkumar472/cf-git'; import { SqliteFS, type SqlExecutor } from './fs-adapter'; import { FileOutputType } from '../schemas'; @@ -28,24 +28,24 @@ export class GitVersionControl { } async init(): Promise { - // Initialize git repository (isomorphic-git operations are async) + // Initialize git repository (isomorphic-git init is idempotent - safe to call multiple times) try { - console.log('Checking for git repository...'); - this.fs.readdir('/.git'); - console.log('Git repository found'); - } catch { - console.log('Git repository not found, initializing...'); - try { - await git.init({ fs: this.fs, dir: '/', defaultBranch: 'main' }); - } catch (error) { - console.error(`Failed to initialize git repository:`, error); - } + const startTime = Date.now(); + console.log('[Git] Initializing repository...'); + await git.init({ fs: this.fs, dir: '/', defaultBranch: 'main' }); + const duration = Date.now() - startTime; + console.log(`[Git] Repository initialized in ${duration}ms`); + } catch (error) { + // Init might fail if already initialized, which is fine + console.log('[Git] Repository already initialized or init skipped:', error); } } async commit(files: FileSnapshot[], message?: string): Promise { if (!files.length) throw new Error('Cannot create empty commit'); + console.log(`[Git] Starting commit with ${files.length} files`); + // Normalize paths (remove leading slashes for git) const normalizedFiles = files.map(f => ({ path: f.filePath.startsWith('/') ? f.filePath.slice(1) : f.filePath, @@ -53,15 +53,28 @@ export class GitVersionControl { })); // Write and stage files first - for (const file of normalizedFiles) { + for (let i = 0; i < normalizedFiles.length; i++) { + const file = normalizedFiles[i]; try { - this.fs.writeFile(file.path, file.content); // Synchronous - await git.add({ fs: this.fs, dir: '/', filepath: file.path }); + console.log(`[Git] Processing file ${i + 1}/${normalizedFiles.length}: ${file.path}`); + await this.fs.writeFile(file.path, file.content); + + await git.add({ + fs: this.fs, + dir: '/', + filepath: file.path, + cache: {} + }); + + console.log(`[Git] Staged ${i + 1}/${normalizedFiles.length}: ${file.path}`); } catch (error) { - throw new Error(`Failed to write/stage file ${file.path}: ${error instanceof Error ? error.message : String(error)}`); + console.error(`[Git] Failed to stage file ${file.path}:`, error); + throw new Error(`Failed to stage file ${file.path}: ${error instanceof Error ? error.message : String(error)}`); } } + console.log('[Git] All files written and staged, checking for changes...'); + // Check if there are actual changes (compare staged vs HEAD) let hasChanges = false; try { @@ -69,16 +82,20 @@ export class GitVersionControl { // row[1] = HEAD index, row[2] = STAGE index // If they differ, we have changes to commit hasChanges = status.some(row => row[1] !== row[2]); + console.log(`[Git] Status check: ${hasChanges ? 'has changes' : 'no changes'}`); } catch (e) { // First commit or error, assume changes + console.log('[Git] Status check failed (likely first commit), assuming changes'); hasChanges = true; } if (!hasChanges) { + console.log('[Git] No actual changes to commit'); return null; // No actual changes to commit } - return git.commit({ + console.log('[Git] Creating commit...'); + const oid = await git.commit({ fs: this.fs, dir: '/', message: message || `Auto-checkpoint (${new Date().toISOString()})`, @@ -88,6 +105,8 @@ export class GitVersionControl { timestamp: Math.floor(Date.now() / 1000) } }); + console.log(`[Git] Commit created: ${oid}`); + return oid; } async log(limit = 50): Promise { diff --git a/worker/agents/services/implementations/CodingAgent.ts b/worker/agents/services/implementations/CodingAgent.ts index 9040f211..1becb4be 100644 --- a/worker/agents/services/implementations/CodingAgent.ts +++ b/worker/agents/services/implementations/CodingAgent.ts @@ -116,12 +116,4 @@ export class CodingAgentInterface { ): Promise { return this.agentStub.executeDeepDebug(issue, toolRenderer, streamCb, focusPaths); } - - handleGitInfoRefs(): Promise { - return this.agentStub.handleGitInfoRefs(); - } - - handleGitUploadPack(): Promise { - return this.agentStub.handleGitUploadPack(); - } } diff --git a/worker/agents/services/implementations/FileManager.ts b/worker/agents/services/implementations/FileManager.ts index 1a9bd50f..8e174d48 100644 --- a/worker/agents/services/implementations/FileManager.ts +++ b/worker/agents/services/implementations/FileManager.ts @@ -38,11 +38,12 @@ export class FileManager implements IFileManager { return FileProcessing.getAllFiles(this.getTemplateDetailsFunc(), state.generatedFilesMap); } - saveGeneratedFile(file: FileOutputType, commitMessage: string): FileState { - return this.saveGeneratedFiles([file], commitMessage)[0]; + async saveGeneratedFile(file: FileOutputType, commitMessage: string): Promise { + const results = await this.saveGeneratedFiles([file], commitMessage); + return results[0]; } - saveGeneratedFiles(files: FileOutputType[], commitMessage: string): FileState[] { + async saveGeneratedFiles(files: FileOutputType[], commitMessage: string): Promise { const filesMap = { ...this.stateManager.getState().generatedFilesMap }; const fileStates: FileState[] = []; @@ -84,9 +85,11 @@ export class FileManager implements IFileManager { }); try { - this.git.commit(fileStates, commitMessage); + console.log(`[FileManager] Committing ${fileStates.length} files:`, commitMessage); + await this.git.commit(fileStates, commitMessage); + console.log(`[FileManager] Commit successful`); } catch (error) { - console.error(`Failed to commit files:`, error, commitMessage); + console.error(`[FileManager] Failed to commit files:`, error, commitMessage); } return fileStates; } diff --git a/worker/agents/services/interfaces/ICodingAgent.ts b/worker/agents/services/interfaces/ICodingAgent.ts index f34564da..e71a331b 100644 --- a/worker/agents/services/interfaces/ICodingAgent.ts +++ b/worker/agents/services/interfaces/ICodingAgent.ts @@ -59,8 +59,4 @@ export abstract class ICodingAgent { streamCb: (chunk: string) => void, focusPaths?: string[], ): Promise; - - // Git clone protocol handlers - abstract handleGitInfoRefs(): Promise; - abstract handleGitUploadPack(): Promise; } diff --git a/worker/agents/services/interfaces/IFileManager.ts b/worker/agents/services/interfaces/IFileManager.ts index 2bc27467..84164b34 100644 --- a/worker/agents/services/interfaces/IFileManager.ts +++ b/worker/agents/services/interfaces/IFileManager.ts @@ -1,5 +1,5 @@ import { FileOutputType } from '../../schemas'; -// import { TemplateDetails } from '../../../services/sandbox/sandboxTypes'; +import { FileState } from '../../core/state'; /** * Interface for file management operations @@ -24,12 +24,12 @@ export interface IFileManager { /** * Save a generated file */ - saveGeneratedFile(file: FileOutputType, commitMessage: string): void; + saveGeneratedFile(file: FileOutputType, commitMessage: string): Promise; /** * Save multiple generated files */ - saveGeneratedFiles(files: FileOutputType[], commitMessage: string): void; + saveGeneratedFiles(files: FileOutputType[], commitMessage: string): Promise; /** * Delete files from the file manager diff --git a/worker/api/controllers/appView/controller.ts b/worker/api/controllers/appView/controller.ts index 1dccca51..3ff59a08 100644 --- a/worker/api/controllers/appView/controller.ts +++ b/worker/api/controllers/appView/controller.ts @@ -215,10 +215,13 @@ export class AppViewController extends BaseController { context: RouteContext ): Promise { try { - const appId = context.pathParams.id; - if (!appId) { + this.logger.info('Handling git info/refs request'); + // Strip .git suffix from the captured ID (e.g., "abc123.git" -> "abc123") + const appIdWithGit = context.pathParams.id; + if (!appIdWithGit) { return new Response('App ID is required', { status: 400 }); } + const appId = appIdWithGit.replace(/\.git$/, ''); // Check access const hasAccess = await this.verifyGitAccess(request, env, appId); @@ -259,10 +262,12 @@ export class AppViewController extends BaseController { context: RouteContext ): Promise { try { - const appId = context.pathParams.id; - if (!appId) { + // Strip .git suffix from the captured ID (e.g., "abc123.git" -> "abc123") + const appIdWithGit = context.pathParams.id; + if (!appIdWithGit) { return new Response('App ID is required', { status: 400 }); } + const appId = appIdWithGit.replace(/\.git$/, ''); // Check access const hasAccess = await this.verifyGitAccess(request, env, appId); diff --git a/worker/api/handlers/git-protocol.ts b/worker/api/handlers/git-protocol.ts new file mode 100644 index 00000000..09c3c080 --- /dev/null +++ b/worker/api/handlers/git-protocol.ts @@ -0,0 +1,109 @@ +/** + * Git Protocol Handler + * Handles git clone/fetch operations via HTTP protocol + * Route: /apps/:id.git/info/refs and /apps/:id.git/git-upload-pack + */ +import { getAgentStub } from '../../agents'; +import { createLogger } from '../../logger'; + +const logger = createLogger('GitProtocol'); + +/** + * Git protocol route patterns + */ +const GIT_INFO_REFS_PATTERN = /^\/apps\/([a-f0-9-]+)\.git\/info\/refs$/; +const GIT_UPLOAD_PACK_PATTERN = /^\/apps\/([a-f0-9-]+)\.git\/git-upload-pack$/; + +/** + * Check if request is a Git protocol request + */ +export function isGitProtocolRequest(pathname: string): boolean { + return GIT_INFO_REFS_PATTERN.test(pathname) || GIT_UPLOAD_PACK_PATTERN.test(pathname); +} + +/** + * Extract app ID from Git protocol URL + */ +function extractAppId(pathname: string): string | null { + const infoRefsMatch = pathname.match(GIT_INFO_REFS_PATTERN); + if (infoRefsMatch) return infoRefsMatch[1]; + + const uploadPackMatch = pathname.match(GIT_UPLOAD_PACK_PATTERN); + if (uploadPackMatch) return uploadPackMatch[1]; + + return null; +} + +/** + * Handle Git info/refs request + */ +async function handleInfoRefs(env: Env, appId: string): Promise { + try { + const agentStub = await getAgentStub(env, appId, true, logger); + if (!agentStub || !(await agentStub.isInitialized())) { + return new Response('Repository not found', { status: 404 }); + } + + const response = await agentStub.handleGitInfoRefs(); + return new Response(response, { + status: 200, + headers: { + 'Content-Type': 'application/x-git-upload-pack-advertisement', + 'Cache-Control': 'no-cache' + } + }); + } catch (error) { + logger.error('Git info/refs error:', error); + return new Response('Internal server error', { status: 500 }); + } +} + +/** + * Handle Git upload-pack request + */ +async function handleUploadPack(env: Env, appId: string): Promise { + try { + const agentStub = await getAgentStub(env, appId, true, logger); + if (!agentStub || !(await agentStub.isInitialized())) { + return new Response('Repository not found', { status: 404 }); + } + + const packfile = await agentStub.handleGitUploadPack(); + return new Response(packfile, { + status: 200, + headers: { + 'Content-Type': 'application/x-git-upload-pack-result', + 'Cache-Control': 'no-cache' + } + }); + } catch (error) { + logger.error('Git upload-pack error:', error); + return new Response('Internal server error', { status: 500 }); + } +} + +/** + * Main handler for Git protocol requests + */ +export async function handleGitProtocolRequest( + request: Request, + env: Env +): Promise { + const url = new URL(request.url); + const pathname = url.pathname; + + // Extract app ID + const appId = extractAppId(pathname); + if (!appId) { + return new Response('Invalid Git URL', { status: 400 }); + } + + // Route to appropriate handler + if (GIT_INFO_REFS_PATTERN.test(pathname)) { + return handleInfoRefs(env, appId); + } else if (GIT_UPLOAD_PACK_PATTERN.test(pathname)) { + return handleUploadPack(env, appId); + } + + return new Response('Not found', { status: 404 }); +} diff --git a/worker/api/routes/appRoutes.ts b/worker/api/routes/appRoutes.ts index 6c7eb4b9..47d3500d 100644 --- a/worker/api/routes/appRoutes.ts +++ b/worker/api/routes/appRoutes.ts @@ -72,10 +72,6 @@ export function setupAppRoutes(app: Hono): void { // Generate git clone token for private repos - OWNER ONLY appRouter.post('/:id/git/token', setAuthLevel(AuthConfig.ownerOnly), adaptController(AppViewController, AppViewController.generateGitCloneToken)); - // Git protocol endpoints (public with internal access check) - // Note: These return raw Response (not JSON), but adaptController handles that fine - appRouter.get('/:id.git/info/refs', setAuthLevel(AuthConfig.public), adaptController(AppViewController, AppViewController.handleGitInfoRefs)); - appRouter.post('/:id.git/git-upload-pack', setAuthLevel(AuthConfig.public), adaptController(AppViewController, AppViewController.handleGitUploadPack)); // Mount the app router under /api/apps app.route('/api/apps', appRouter); diff --git a/worker/index.ts b/worker/index.ts index 69967c87..a8e7872d 100644 --- a/worker/index.ts +++ b/worker/index.ts @@ -9,6 +9,7 @@ import { getPreviewDomain } from './utils/urls'; import { proxyToAiGateway } from './services/aigateway-proxy/controller'; import { isOriginAllowed } from './config/security'; import { proxyToSandbox } from './services/sandbox/request-handler'; +import { handleGitProtocolRequest, isGitProtocolRequest } from './api/handlers/git-protocol'; // Durable Object and Service exports export { UserAppSandboxService, DeployerService } from './services/sandbox/sandboxSdkClient'; @@ -110,7 +111,7 @@ async function handleUserAppRequest(request: Request, env: Env): Promise { - // logger.info(`Received request: ${request.method} ${request.url}`); + logger.info(`Received request: ${request.method} ${request.url}`); // --- Pre-flight Checks --- // 1. Critical configuration check: Ensure custom domain is set. @@ -140,6 +141,12 @@ const worker = { // Route 1: Main Platform Request (e.g., build.cloudflare.dev or localhost) if (isMainDomainRequest) { + // Handle Git protocol endpoints directly + // Route: /apps/:id.git/info/refs or /apps/:id.git/git-upload-pack + if (isGitProtocolRequest(pathname)) { + return handleGitProtocolRequest(request, env); + } + // Serve static assets for all non-API routes from the ASSETS binding. if (!pathname.startsWith('/api/')) { return env.ASSETS.fetch(request); From 0dafe7837819cc2cc9d34a986817a62e52e3ecca Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sat, 25 Oct 2025 12:47:36 -0400 Subject: [PATCH 077/150] refactor: migrate git clone service from DO to worker for better memory usage --- worker/agents/core/simpleGeneratorAgent.ts | 73 +++++------- worker/agents/git/fs-adapter.ts | 87 +++++++++----- worker/agents/git/git-clone-service.ts | 128 +++++++-------------- worker/api/handlers/git-protocol.ts | 44 ++++++- 4 files changed, 175 insertions(+), 157 deletions(-) diff --git a/worker/agents/core/simpleGeneratorAgent.ts b/worker/agents/core/simpleGeneratorAgent.ts index 0a1e735c..e0fbf7c6 100644 --- a/worker/agents/core/simpleGeneratorAgent.ts +++ b/worker/agents/core/simpleGeneratorAgent.ts @@ -49,7 +49,6 @@ import { ConversationMessage, ConversationState } from '../inferutils/common'; import { DeepCodeDebugger } from '../assistants/codeDebugger'; import { DeepDebugResult } from './types'; import { StateMigration } from './stateMigration'; -import { GitCloneService } from '../git/git-clone-service'; interface Operations { codeReview: CodeReviewOperation; @@ -2343,16 +2342,22 @@ export class SimpleCodeGeneratorAgent extends Agent { } /** - * Handle git info/refs request for cloning - * Returns git protocol advertisement + * Export git objects + * The route handler will build the repo with template rebasing */ - async handleGitInfoRefs(): Promise { + async exportGitObjects(): Promise<{ + gitObjects: Array<{ path: string; data: Uint8Array }>; + query: string; + hasCommits: boolean; + templateDetails: TemplateDetails | null; + }> { try { - console.log('[Agent] handleGitInfoRefs called'); + console.log('[Agent] exportGitObjects called'); // Ensure git has at least one commit before allowing clone const head = await this.git.getHead(); console.log('[Agent] git.getHead() returned:', head); + if (!head) { console.log('[Agent] No HEAD found, creating initial commit from current files'); @@ -2368,50 +2373,32 @@ export class SimpleCodeGeneratorAgent extends Agent { await this.git.commit(files, `Initial commit\n\nGenerated by Vibesdk\nQuery: ${this.state.query || 'N/A'}`); console.log('[Agent] Initial commit created with', files.length, 'files'); } else { - console.log('[Agent] No files to commit, returning empty repo'); - // Return empty advertisement for truly empty repos - return '001e# service=git-upload-pack\n0000'; + console.log('[Agent] No files to commit, returning empty'); + await this.ensureTemplateDetails(); + return { + gitObjects: [], + query: this.state.query || 'N/A', + hasCommits: false, + templateDetails: this.templateDetailsCache + }; } } - // Build repository with template rebasing - const repoFS = await GitCloneService.buildRepository({ - agentGitFS: this.git.fs, - templateDetails: this.templateDetailsCache || null, - appQuery: this.state.query || 'N/A' - }); + // Export git objects efficiently (minimal DO memory usage) + const gitObjects = this.git.fs.exportGitObjects(); + console.log('[Agent] Exported', gitObjects.length, 'git objects'); - // Use GitCloneService to handle info/refs - const result = await GitCloneService.handleInfoRefs(repoFS); - console.log('[Agent] handleGitInfoRefs completed, response length:', result.length); - return result; - } catch (error) { - console.error('[Agent] handleGitInfoRefs failed:', error); - throw error; - } - } - - /** - * Handle git upload-pack request for cloning - * Returns packfile for git clone - */ - async handleGitUploadPack(): Promise { - try { - console.log('[Agent] handleGitUploadPack called'); + // Ensure template details are available + await this.ensureTemplateDetails(); - // Build repository with template rebasing - const repoFS = await GitCloneService.buildRepository({ - agentGitFS: this.git.fs, - templateDetails: this.templateDetailsCache || null, - appQuery: this.state.query || 'N/A' - }); - - // Use GitCloneService to handle upload-pack - const packfile = await GitCloneService.handleUploadPack(repoFS); - console.log('[Agent] handleGitUploadPack completed, packfile size:', packfile.length); - return packfile; + return { + gitObjects, + query: this.state.query || 'N/A', + hasCommits: gitObjects.length > 0, + templateDetails: this.templateDetailsCache + }; } catch (error) { - console.error('[Agent] handleGitUploadPack failed:', error); + console.error('[Agent] exportGitObjects failed:', error); throw error; } } diff --git a/worker/agents/git/fs-adapter.ts b/worker/agents/git/fs-adapter.ts index 5cc65c83..47f15521 100644 --- a/worker/agents/git/fs-adapter.ts +++ b/worker/agents/git/fs-adapter.ts @@ -57,6 +57,7 @@ export class SqliteFS { this.sql` CREATE TABLE IF NOT EXISTS git_objects ( path TEXT PRIMARY KEY, + parent_path TEXT NOT NULL DEFAULT '', data TEXT NOT NULL, is_dir INTEGER NOT NULL DEFAULT 0, mtime INTEGER NOT NULL @@ -64,11 +65,11 @@ export class SqliteFS { `; // Create indexes for efficient lookups - this.sql`CREATE INDEX IF NOT EXISTS idx_git_objects_path ON git_objects(path)`; + this.sql`CREATE INDEX IF NOT EXISTS idx_git_objects_parent ON git_objects(parent_path, path)`; this.sql`CREATE INDEX IF NOT EXISTS idx_git_objects_is_dir ON git_objects(is_dir, path)`; // Ensure root directory exists - this.sql`INSERT OR IGNORE INTO git_objects (path, data, is_dir, mtime) VALUES ('', '', 1, ${Date.now()})`; + this.sql`INSERT OR IGNORE INTO git_objects (path, parent_path, data, is_dir, mtime) VALUES ('', '', '', 1, ${Date.now()})`; // Make promises property enumerable for isomorphic-git FileSystem detection Object.defineProperty(this, 'promises', { @@ -150,18 +151,28 @@ export class SqliteFS { // Ensure parent directories exist (git implicitly creates them) const parts = normalized.split('/'); + const parentPath = parts.length > 1 ? parts.slice(0, -1).join('/') : ''; + if (parts.length > 1) { const now = Date.now(); for (let i = 0; i < parts.length - 1; i++) { const dirPath = parts.slice(0, i + 1).join('/'); - this.sql`INSERT OR IGNORE INTO git_objects (path, data, is_dir, mtime) VALUES (${dirPath}, '', 1, ${now})`; + const dirParent = i === 0 ? '' : parts.slice(0, i).join('/'); + this.sql`INSERT OR IGNORE INTO git_objects (path, parent_path, data, is_dir, mtime) VALUES (${dirPath}, ${dirParent}, '', 1, ${now})`; } } - // Encode to base64 for safe storage (handle empty files) - const base64Content = bytes.length === 0 ? '' : btoa(String.fromCharCode(...Array.from(bytes))); + // Encode to base64 for safe storage + let base64Content = ''; + if (bytes.length > 0) { + let binaryString = ''; + for (let i = 0; i < bytes.length; i++) { + binaryString += String.fromCharCode(bytes[i]); + } + base64Content = btoa(binaryString); + } - this.sql`INSERT OR REPLACE INTO git_objects (path, data, is_dir, mtime) VALUES (${normalized}, ${base64Content}, 0, ${Date.now()})`; + this.sql`INSERT OR REPLACE INTO git_objects (path, parent_path, data, is_dir, mtime) VALUES (${normalized}, ${parentPath}, ${base64Content}, 0, ${Date.now()})`; console.log(`[Git FS] writeFile: ${normalized} -> ${bytes.length} bytes written - COMPLETE`); } @@ -206,29 +217,18 @@ export class SqliteFS { throw error; } - let rows; - if (normalized === '') { - // Root directory - get all direct children - rows = this.sql<{ path: string }>`SELECT path FROM git_objects WHERE path != '' AND path NOT LIKE '%/%'`; - } else { - // Subdirectory - get direct children only - rows = this.sql<{ path: string }>`SELECT path FROM git_objects WHERE path LIKE ${normalized + '/%'}`; - } + const rows = this.sql<{ path: string }>`SELECT path FROM git_objects WHERE parent_path = ${normalized}`; if (!rows || rows.length === 0) return []; - const children = new Set(); - const prefixLen = normalized ? normalized.length + 1 : 0; - - for (const row of rows) { - const relativePath = normalized ? row.path.substring(prefixLen) : row.path; - const first = relativePath.split('/')[0]; - if (first) children.add(first); - } + // Extract just the basename from each path + const children = rows.map(row => { + const parts = row.path.split('/'); + return parts[parts.length - 1]; + }); - const result = Array.from(children); - console.log(`[Git FS] readdir: ${normalized} -> [${result.join(', ')}] (${result.length} entries) - COMPLETE`); - return result; + console.log(`[Git FS] readdir: ${normalized} -> [${children.join(', ')}] (${children.length} entries) - COMPLETE`); + return children; } async mkdir(path: string, _options?: { recursive?: boolean }): Promise { @@ -276,8 +276,9 @@ export class SqliteFS { } } - // Create directory entry - this.sql`INSERT OR IGNORE INTO git_objects (path, data, is_dir, mtime) VALUES (${normalized}, '', 1, ${Date.now()})`; + // Create directory entry (reuse parts from earlier) + const parentPath = parts.length > 1 ? parts.slice(0, -1).join('/') : ''; + this.sql`INSERT OR IGNORE INTO git_objects (path, parent_path, data, is_dir, mtime) VALUES (${normalized}, ${parentPath}, '', 1, ${Date.now()})`; console.log(`[Git FS] mkdir: ${normalized} created - COMPLETE`); } @@ -408,4 +409,36 @@ export class SqliteFS { console.log(`[Git FS] write: ${path}`); return await this.writeFile(path, data); } + + /** + * Export all git objects for cloning + * Returns array of {path, data} + */ + exportGitObjects(): Array<{ path: string; data: Uint8Array }> { + console.log('[Git FS] Exporting git objects...'); + const objects = this.sql<{ path: string; data: string; is_dir: number }>` + SELECT path, data, is_dir FROM git_objects WHERE path LIKE '.git/%' + `; + + const exported: Array<{ path: string; data: Uint8Array }> = []; + + for (const obj of objects) { + if (obj.is_dir === 1) continue; // Skip directories, only export files + + // Decode base64 to binary + const binaryString = atob(obj.data); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + exported.push({ + path: '/' + obj.path, // Add leading slash for consistency + data: bytes + }); + } + + console.log(`[Git FS] Exported ${exported.length} git objects`); + return exported; + } } \ No newline at end of file diff --git a/worker/agents/git/git-clone-service.ts b/worker/agents/git/git-clone-service.ts index de4e7c15..5a4b92a0 100644 --- a/worker/agents/git/git-clone-service.ts +++ b/worker/agents/git/git-clone-service.ts @@ -5,14 +5,13 @@ import git from '@ashishkumar472/cf-git'; import { MemFS } from './memfs'; -import { SqliteFS } from './fs-adapter'; import { createLogger } from '../../logger'; import type { TemplateDetails as SandboxTemplateDetails } from '../../services/sandbox/sandboxTypes'; const logger = createLogger('GitCloneService'); export interface RepositoryBuildOptions { - agentGitFS: SqliteFS; + gitObjects: Array<{ path: string; data: Uint8Array }>; templateDetails: SandboxTemplateDetails | null | undefined; appQuery: string; } @@ -23,20 +22,20 @@ export class GitCloneService { * * Strategy: * 1. Create base commit with template files - * 2. Export all git objects from agent's repo (SqliteFS) - * 3. Import git objects into MemFS - * 4. Update refs to point to agent's commits + * 2. Import exported git objects from agent + * 3. Update refs to point to agent's commits * * Result: Template base + agent's commit history on top */ static async buildRepository(options: RepositoryBuildOptions): Promise { - const { agentGitFS, templateDetails, appQuery } = options; + const { gitObjects, templateDetails, appQuery } = options; const fs = new MemFS(); try { logger.info('Building git repository with template rebasing', { templateName: templateDetails?.name, - templateFileCount: templateDetails ? Object.keys(templateDetails.allFiles).length : 0 + templateFileCount: templateDetails ? Object.keys(templateDetails.allFiles).length : 0, + gitObjectCount: gitObjects.length }); // Step 1: Create base commit with template files @@ -55,8 +54,8 @@ export class GitCloneService { dir: '/', message: `Template: ${templateDetails.name}\n\nBase template for Vibesdk application\nQuery: ${appQuery}`, author: { - name: 'Vibesdk Template', - email: 'templates@cloudflare.dev', + name: 'Vibesdk Agent', + email: 'vibesdk-bot@cloudflare.dev', timestamp: Math.floor(Date.now() / 1000) } }); @@ -64,19 +63,20 @@ export class GitCloneService { logger.info('Created template base commit', { oid: templateCommitOid }); } - // Step 2: Check if agent has git history - const agentHasGitRepo = await this.hasGitRepository(agentGitFS); - - if (agentHasGitRepo) { - // Step 3: Export all git objects from agent's repo and import to MemFS - await this.copyGitObjects(agentGitFS, fs); + // Step 2: Import exported git objects from agent + if (gitObjects.length > 0) { + logger.info('Importing git objects from agent', { count: gitObjects.length }); + + for (const obj of gitObjects) { + fs.writeFile(obj.path, obj.data); + } - // Step 4: Get agent's HEAD + // Step 3: Get agent's HEAD and update main branch try { - const agentHeadOid = await git.resolveRef({ fs: agentGitFS, dir: '/', ref: 'HEAD' }); + const agentHeadOid = await git.resolveRef({ fs, dir: '/', ref: 'HEAD' }); // Update main branch to point to agent's HEAD - // This effectively rebases agent's commits on top of template + // This rebases agent's commits on top of template await git.writeRef({ fs, dir: '/', @@ -104,63 +104,6 @@ export class GitCloneService { } } - /** - * Check if agent has initialized git repository - */ - private static async hasGitRepository(agentFS: SqliteFS): Promise { - try { - agentFS.readdir('/.git'); - return true; - } catch { - return false; - } - } - - /** - * Copy all git objects from agent's SqliteFS to MemFS - * This includes commits, trees, and blobs - */ - private static async copyGitObjects(sourceFS: SqliteFS, targetFS: MemFS): Promise { - try { - // Copy the entire .git directory structure - await this.copyDirectory(sourceFS, targetFS, '/.git'); - logger.info('Copied git objects from agent to MemFS'); - } catch (error) { - logger.error('Failed to copy git objects', { error }); - throw error; - } - } - - /** - * Recursively copy directory from source to target filesystem - */ - private static async copyDirectory( - sourceFS: SqliteFS, - targetFS: MemFS, - dirPath: string - ): Promise { - const entries = await sourceFS.readdir(dirPath); - - for (const entry of entries) { - const fullPath = `${dirPath}/${entry}`; - - try { - const stat = await sourceFS.stat(fullPath); - - if (stat.type === 'file') { - // Copy file - const data = await sourceFS.readFile(fullPath); - targetFS.writeFile(fullPath, data as Uint8Array); - } else if (stat.type === 'dir') { - // Recursively copy directory - await this.copyDirectory(sourceFS, targetFS, fullPath); - } - } catch (error) { - logger.warn(`Failed to copy ${fullPath}`, { error }); - // Continue with other files - } - } - } /** * Handle git info/refs request @@ -200,36 +143,51 @@ export class GitCloneService { */ static async handleUploadPack(fs: MemFS): Promise { try { - // For initial implementation, send all objects - // Future optimization: parse client wants from request body const head = await git.resolveRef({ fs, dir: '/', ref: 'HEAD' }); + const objects = new Set(); - // Walk the commit tree to get all objects - const { commit } = await git.readCommit({ fs, dir: '/', oid: head }); - const objects = [head, commit.tree]; + const walkCommits = async (oid: string): Promise => { + if (objects.has(oid)) return; // Already visited + objects.add(oid); + + const { commit } = await git.readCommit({ fs, dir: '/', oid }); + objects.add(commit.tree); + + // Recursively collect all tree and blob objects from this commit + await collectTreeObjects(commit.tree); + + // Walk parent commits recursively + for (const parentOid of commit.parent) { + await walkCommits(parentOid); + } + }; // Recursively collect all tree and blob objects const collectTreeObjects = async (treeOid: string): Promise => { - objects.push(treeOid); + if (objects.has(treeOid)) return; + objects.add(treeOid); + const { tree } = await git.readTree({ fs, dir: '/', oid: treeOid }); for (const entry of tree) { - objects.push(entry.oid); + objects.add(entry.oid); if (entry.type === 'tree') { await collectTreeObjects(entry.oid); } } }; - await collectTreeObjects(commit.tree); + await walkCommits(head); - logger.info('Generating packfile', { objectCount: objects.length }); + logger.info('Generating packfile with full commit history', { + objectCount: objects.size + }); // Create packfile with all objects const packResult = await git.packObjects({ fs, dir: '/', - oids: objects + oids: Array.from(objects) }); // packObjects returns { packfile: Uint8Array } diff --git a/worker/api/handlers/git-protocol.ts b/worker/api/handlers/git-protocol.ts index 09c3c080..37458feb 100644 --- a/worker/api/handlers/git-protocol.ts +++ b/worker/api/handlers/git-protocol.ts @@ -2,9 +2,12 @@ * Git Protocol Handler * Handles git clone/fetch operations via HTTP protocol * Route: /apps/:id.git/info/refs and /apps/:id.git/git-upload-pack + * + * Architecture: Export git objects from DO, build repo in worker to save DO memory */ import { getAgentStub } from '../../agents'; import { createLogger } from '../../logger'; +import { GitCloneService } from '../../agents/git/git-clone-service'; const logger = createLogger('GitProtocol'); @@ -44,7 +47,29 @@ async function handleInfoRefs(env: Env, appId: string): Promise { return new Response('Repository not found', { status: 404 }); } - const response = await agentStub.handleGitInfoRefs(); + // Export git objects from DO + const { gitObjects, query, hasCommits, templateDetails } = await agentStub.exportGitObjects(); + + if (!hasCommits) { + // Return empty advertisement for repos with no commits + return new Response('001e# service=git-upload-pack\n0000', { + status: 200, + headers: { + 'Content-Type': 'application/x-git-upload-pack-advertisement', + 'Cache-Control': 'no-cache' + } + }); + } + + // Build repository in worker + const repoFS = await GitCloneService.buildRepository({ + gitObjects, + templateDetails, + appQuery: query + }); + + // Generate info/refs response + const response = await GitCloneService.handleInfoRefs(repoFS); return new Response(response, { status: 200, headers: { @@ -68,7 +93,22 @@ async function handleUploadPack(env: Env, appId: string): Promise { return new Response('Repository not found', { status: 404 }); } - const packfile = await agentStub.handleGitUploadPack(); + // Export git objects from DO + const { gitObjects, query, hasCommits, templateDetails } = await agentStub.exportGitObjects(); + + if (!hasCommits) { + return new Response('No commits to pack', { status: 404 }); + } + + // Build repository in worker + const repoFS = await GitCloneService.buildRepository({ + gitObjects, + templateDetails, + appQuery: query + }); + + // Generate packfile with full commit history + const packfile = await GitCloneService.handleUploadPack(repoFS); return new Response(packfile, { status: 200, headers: { From ff9643afb7d79ac6f58e2b2862b0681e7f94b6f2 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sat, 25 Oct 2025 12:49:41 -0400 Subject: [PATCH 078/150] fix: add ownership/access checks in git protocol handlers --- worker/api/controllers/appView/controller.ts | 143 ------------------- worker/api/handlers/git-protocol.ts | 71 ++++++++- 2 files changed, 67 insertions(+), 147 deletions(-) diff --git a/worker/api/controllers/appView/controller.ts b/worker/api/controllers/appView/controller.ts index 3ff59a08..7bff976b 100644 --- a/worker/api/controllers/appView/controller.ts +++ b/worker/api/controllers/appView/controller.ts @@ -204,147 +204,4 @@ export class AppViewController extends BaseController { } } - /** - * Handle git info/refs request - * GET /app/:id.git/info/refs?service=git-upload-pack - */ - static async handleGitInfoRefs( - request: Request, - env: Env, - _ctx: ExecutionContext, - context: RouteContext - ): Promise { - try { - this.logger.info('Handling git info/refs request'); - // Strip .git suffix from the captured ID (e.g., "abc123.git" -> "abc123") - const appIdWithGit = context.pathParams.id; - if (!appIdWithGit) { - return new Response('App ID is required', { status: 400 }); - } - const appId = appIdWithGit.replace(/\.git$/, ''); - - // Check access - const hasAccess = await this.verifyGitAccess(request, env, appId); - if (!hasAccess) { - return new Response('Repository not found', { status: 404 }); - } - - // Get agent and handle git info/refs - const agentStub = await getAgentStub(env, appId, true, this.logger); - if (!agentStub || !(await agentStub.isInitialized())) { - return new Response('Repository not found', { status: 404 }); - } - - // Agent handles everything internally - const response = await agentStub.handleGitInfoRefs(); - - return new Response(response, { - status: 200, - headers: { - 'Content-Type': 'application/x-git-upload-pack-advertisement', - 'Cache-Control': 'no-cache' - } - }); - } catch (error) { - this.logger.error('Error handling git info/refs:', error); - return new Response('Internal server error', { status: 500 }); - } - } - - /** - * Handle git upload-pack request (actual clone) - * POST /app/:id.git/git-upload-pack - */ - static async handleGitUploadPack( - request: Request, - env: Env, - _ctx: ExecutionContext, - context: RouteContext - ): Promise { - try { - // Strip .git suffix from the captured ID (e.g., "abc123.git" -> "abc123") - const appIdWithGit = context.pathParams.id; - if (!appIdWithGit) { - return new Response('App ID is required', { status: 400 }); - } - const appId = appIdWithGit.replace(/\.git$/, ''); - - // Check access - const hasAccess = await this.verifyGitAccess(request, env, appId); - if (!hasAccess) { - return new Response('Repository not found', { status: 404 }); - } - - // Get agent and handle git upload-pack - const agentStub = await getAgentStub(env, appId, true, this.logger); - if (!agentStub || !(await agentStub.isInitialized())) { - return new Response('Repository not found', { status: 404 }); - } - - // Agent handles everything internally - const packfile = await agentStub.handleGitUploadPack(); - - return new Response(packfile, { - status: 200, - headers: { - 'Content-Type': 'application/x-git-upload-pack-result', - 'Cache-Control': 'no-cache' - } - }); - } catch (error) { - this.logger.error('Error handling git upload-pack:', error); - return new Response('Internal server error', { status: 500 }); - } - } - - /** - * Verify git access (public apps or owner with valid token) - */ - private static async verifyGitAccess( - request: Request, - env: Env, - appId: string - ): Promise { - // Get app using app service - const appService = new AppService(env); - const app = await appService.getAppDetails(appId); - - if (!app) { - return false; - } - - // Public apps: anyone can clone - if (app.visibility === 'public') { - return true; - } - - // Private apps: require authentication - // Check for token in Authorization header or URL - const authHeader = request.headers.get('Authorization'); - let token: string | null = null; - - if (authHeader?.startsWith('Bearer ')) { - token = authHeader.slice(7); - } else if (authHeader?.startsWith('Basic ')) { - // Git sends credentials as Basic auth - const decoded = atob(authHeader.slice(6)); - const [username, password] = decoded.split(':'); - token = password || username; // Token might be in either field - } - - if (!token) { - return false; - } - - // Verify token using JWTUtils - const jwtUtils = JWTUtils.getInstance(env); - const payload = await jwtUtils.verifyToken(token); - - if (!payload) { - return false; - } - - // Check if user owns the app - return payload.sub === app.userId; - } } \ No newline at end of file diff --git a/worker/api/handlers/git-protocol.ts b/worker/api/handlers/git-protocol.ts index 37458feb..7c05b240 100644 --- a/worker/api/handlers/git-protocol.ts +++ b/worker/api/handlers/git-protocol.ts @@ -8,6 +8,8 @@ import { getAgentStub } from '../../agents'; import { createLogger } from '../../logger'; import { GitCloneService } from '../../agents/git/git-clone-service'; +import { AppService } from '../../database/services/AppService'; +import { JWTUtils } from '../../utils/jwtUtils'; const logger = createLogger('GitProtocol'); @@ -37,11 +39,66 @@ function extractAppId(pathname: string): string | null { return null; } +/** + * Verify git access (public apps or owner with valid token) + */ +async function verifyGitAccess( + request: Request, + env: Env, + appId: string +): Promise { + const appService = new AppService(env); + const app = await appService.getAppDetails(appId); + + if (!app) { + return false; + } + + // Public apps: anyone can clone + if (app.visibility === 'public') { + return true; + } + + // Private apps: require authentication + const authHeader = request.headers.get('Authorization'); + let token: string | null = null; + + if (authHeader?.startsWith('Bearer ')) { + token = authHeader.slice(7); + } else if (authHeader?.startsWith('Basic ')) { + // Git sends credentials as Basic auth + const decoded = atob(authHeader.slice(6)); + const [username, password] = decoded.split(':'); + token = password || username; + } + + if (!token) { + return false; + } + + // Verify token using JWTUtils + const jwtUtils = JWTUtils.getInstance(env); + const payload = await jwtUtils.verifyToken(token); + + if (!payload) { + return false; + } + + // Check if user owns the app + return payload.sub === app.userId; +} + /** * Handle Git info/refs request */ -async function handleInfoRefs(env: Env, appId: string): Promise { +async function handleInfoRefs(request: Request, env: Env, appId: string): Promise { try { + // Verify access first + const hasAccess = await verifyGitAccess(request, env, appId); + if (!hasAccess) { + return new Response('Repository not found', { status: 404 }); + } + const agentStub = await getAgentStub(env, appId, true, logger); if (!agentStub || !(await agentStub.isInitialized())) { return new Response('Repository not found', { status: 404 }); @@ -86,8 +143,14 @@ async function handleInfoRefs(env: Env, appId: string): Promise { /** * Handle Git upload-pack request */ -async function handleUploadPack(env: Env, appId: string): Promise { +async function handleUploadPack(request: Request, env: Env, appId: string): Promise { try { + // Verify access first + const hasAccess = await verifyGitAccess(request, env, appId); + if (!hasAccess) { + return new Response('Repository not found', { status: 404 }); + } + const agentStub = await getAgentStub(env, appId, true, logger); if (!agentStub || !(await agentStub.isInitialized())) { return new Response('Repository not found', { status: 404 }); @@ -140,9 +203,9 @@ export async function handleGitProtocolRequest( // Route to appropriate handler if (GIT_INFO_REFS_PATTERN.test(pathname)) { - return handleInfoRefs(env, appId); + return handleInfoRefs(request, env, appId); } else if (GIT_UPLOAD_PACK_PATTERN.test(pathname)) { - return handleUploadPack(env, appId); + return handleUploadPack(request, env, appId); } return new Response('Not found', { status: 404 }); From d2e4594771869cbc0f3c8140445bb39ac677ddbb Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sat, 25 Oct 2025 18:46:52 -0400 Subject: [PATCH 079/150] feat: git repos finally working --- worker/agents/git/fs-adapter.ts | 55 +-- worker/agents/git/git-clone-service.ts | 324 ++++++++++++------ worker/agents/git/git.ts | 19 +- worker/agents/git/memfs.ts | 123 ++++--- .../services/implementations/FileManager.ts | 4 - worker/api/handlers/git-protocol.ts | 27 +- 6 files changed, 338 insertions(+), 214 deletions(-) diff --git a/worker/agents/git/fs-adapter.ts b/worker/agents/git/fs-adapter.ts index 47f15521..7eb81a5c 100644 --- a/worker/agents/git/fs-adapter.ts +++ b/worker/agents/git/fs-adapter.ts @@ -71,7 +71,7 @@ export class SqliteFS { // Ensure root directory exists this.sql`INSERT OR IGNORE INTO git_objects (path, parent_path, data, is_dir, mtime) VALUES ('', '', '', 1, ${Date.now()})`; - // Make promises property enumerable for isomorphic-git FileSystem detection + // promises property required for isomorphic-git Object.defineProperty(this, 'promises', { value: this, enumerable: true, @@ -81,12 +81,9 @@ export class SqliteFS { } async readFile(path: string, options?: { encoding?: 'utf8' }): Promise { - // Normalize path (remove leading slashes) const normalized = path.replace(/^\/+/, ''); - console.log(`[Git FS] readFile: ${normalized} (encoding: ${options?.encoding || 'binary'}) - START`); const result = this.sql<{ data: string; is_dir: number }>`SELECT data, is_dir FROM git_objects WHERE path = ${normalized}`; if (!result[0]) { - console.log(`[Git FS] readFile: ${normalized} - ENOENT`); const error: NodeJS.ErrnoException = new Error(`ENOENT: no such file or directory, open '${path}'`); error.code = 'ENOENT'; error.errno = -2; @@ -94,7 +91,6 @@ export class SqliteFS { throw error; } - // Check if it's a directory (directories can't be read as files) if (result[0].is_dir) { const error: NodeJS.ErrnoException = new Error(`EISDIR: illegal operation on a directory, read '${path}'`); error.code = 'EISDIR'; @@ -105,9 +101,7 @@ export class SqliteFS { const base64Data = result[0].data; - // Decode from base64 - handle empty files if (!base64Data) { - console.log(`[Git FS] readFile: ${normalized} -> empty file`); return options?.encoding === 'utf8' ? '' : new Uint8Array(0); } @@ -117,15 +111,11 @@ export class SqliteFS { bytes[i] = binaryString.charCodeAt(i); } - const fileContent = options?.encoding === 'utf8' ? new TextDecoder().decode(bytes) : bytes; - console.log(`[Git FS] readFile: ${normalized} -> ${bytes.length} bytes - COMPLETE`); - return fileContent; + return options?.encoding === 'utf8' ? new TextDecoder().decode(bytes) : bytes; } async writeFile(path: string, data: Uint8Array | string): Promise { - // Normalize path (remove leading slashes) const normalized = path.replace(/^\/+/, ''); - console.log(`[Git FS] writeFile: ${normalized} - START`); if (!normalized) { throw new Error('Cannot write to root'); @@ -173,15 +163,11 @@ export class SqliteFS { } this.sql`INSERT OR REPLACE INTO git_objects (path, parent_path, data, is_dir, mtime) VALUES (${normalized}, ${parentPath}, ${base64Content}, 0, ${Date.now()})`; - console.log(`[Git FS] writeFile: ${normalized} -> ${bytes.length} bytes written - COMPLETE`); } async unlink(path: string): Promise { - // Normalize path (remove leading slashes) const normalized = path.replace(/^\/+/, ''); - console.log(`[Git FS] unlink: ${normalized} - START`); - // Check if exists and is not a directory const existing = this.sql<{ is_dir: number }>`SELECT is_dir FROM git_objects WHERE path = ${normalized}`; if (!existing[0]) { const error: NodeJS.ErrnoException = new Error(`ENOENT: no such file or directory, unlink '${path}'`); @@ -199,15 +185,11 @@ export class SqliteFS { } this.sql`DELETE FROM git_objects WHERE path = ${normalized} AND is_dir = 0`; - console.log(`[Git FS] unlink: ${normalized} -> deleted - COMPLETE`); } async readdir(path: string): Promise { - // Normalize path (remove leading/trailing slashes) const normalized = path.replace(/^\/+|\/+$/g, ''); - console.log(`[Git FS] readdir: ${normalized} - START`); - // Check if directory exists const dirCheck = this.sql<{ is_dir: number }>`SELECT is_dir FROM git_objects WHERE path = ${normalized}`; if (!dirCheck[0] || !dirCheck[0].is_dir) { const error: NodeJS.ErrnoException = new Error(`ENOENT: no such file or directory, scandir '${path}'`); @@ -227,20 +209,14 @@ export class SqliteFS { return parts[parts.length - 1]; }); - console.log(`[Git FS] readdir: ${normalized} -> [${children.join(', ')}] (${children.length} entries) - COMPLETE`); return children; } - async mkdir(path: string, _options?: { recursive?: boolean }): Promise { - // Normalize path (remove leading/trailing slashes) + async mkdir(path: string, _options?: any): Promise { const normalized = path.replace(/^\/+|\/+$/g, ''); - // Don't create root (already exists) if (!normalized) return; - console.log(`[Git FS] mkdir: ${normalized} - START`); - - // Quick check: if parent is root, we can skip parent validation const parts = normalized.split('/'); const isDirectChildOfRoot = parts.length === 1; @@ -263,11 +239,8 @@ export class SqliteFS { const existing = this.sql<{ is_dir: number }>`SELECT is_dir FROM git_objects WHERE path = ${normalized}`; if (existing[0]) { if (existing[0].is_dir === 1) { - // Already exists as directory - this is OK (idempotent) - console.log(`[Git FS] mkdir: ${normalized} already exists - COMPLETE`); return; } else { - // Exists as file - can't create directory const error: NodeJS.ErrnoException = new Error(`EEXIST: file already exists, mkdir '${path}'`); error.code = 'EEXIST'; error.errno = -17; @@ -276,16 +249,12 @@ export class SqliteFS { } } - // Create directory entry (reuse parts from earlier) const parentPath = parts.length > 1 ? parts.slice(0, -1).join('/') : ''; this.sql`INSERT OR IGNORE INTO git_objects (path, parent_path, data, is_dir, mtime) VALUES (${normalized}, ${parentPath}, '', 1, ${Date.now()})`; - console.log(`[Git FS] mkdir: ${normalized} created - COMPLETE`); } async rmdir(path: string): Promise { - // Normalize path (remove leading/trailing slashes) const normalized = path.replace(/^\/+|\/+$/g, ''); - console.log(`[Git FS] rmdir: ${normalized} - START`); if (!normalized) { throw new Error('Cannot remove root directory'); @@ -318,15 +287,11 @@ export class SqliteFS { throw error; } - // Remove the directory this.sql`DELETE FROM git_objects WHERE path = ${normalized}`; - console.log(`[Git FS] rmdir: ${normalized} -> deleted - COMPLETE`); } async stat(path: string): Promise<{ type: 'file' | 'dir'; mode: number; size: number; mtimeMs: number }> { - // Normalize path (remove leading slashes) const normalized = path.replace(/^\/+/, ''); - console.log(`[Git FS] stat: ${normalized} - START`); const result = this.sql<{ data: string; mtime: number; is_dir: number }>`SELECT data, mtime, is_dir FROM git_objects WHERE path = ${normalized}`; if (!result[0]) { const error: NodeJS.ErrnoException = new Error(`ENOENT: no such file or directory, stat '${path}'`); @@ -362,24 +327,20 @@ export class SqliteFS { // Add methods that isomorphic-git expects isFile: () => !isDir, isDirectory: () => isDir, - isSymbolicLink: () => false, // We don't support symlinks yet + isSymbolicLink: () => false }; - console.log(`[Git FS] stat: ${normalized} -> ${statResult.type} (${statResult.size} bytes) - COMPLETE`); return statResult; } async lstat(path: string) { - console.log(`[Git FS] lstat: ${path} (delegating to stat)`); return await this.stat(path); } async symlink(target: string, path: string): Promise { - console.log(`[Git FS] symlink: ${path} -> ${target}`); await this.writeFile(path, target); } async readlink(path: string): Promise { - console.log(`[Git FS] readlink: ${path}`); return (await this.readFile(path, { encoding: 'utf8' })) as string; } @@ -388,14 +349,11 @@ export class SqliteFS { * Required by isomorphic-git's init check */ async exists(path: string): Promise { - console.log(`[Git FS] exists: ${path}`); try { await this.stat(path); - console.log(`[Git FS] exists: ${path} -> true`); return true; } catch (err) { if ((err as NodeJS.ErrnoException).code === 'ENOENT') { - console.log(`[Git FS] exists: ${path} -> false`); return false; } throw err; @@ -406,7 +364,6 @@ export class SqliteFS { * Alias for writeFile (isomorphic-git sometimes uses 'write') */ async write(path: string, data: Uint8Array | string): Promise { - console.log(`[Git FS] write: ${path}`); return await this.writeFile(path, data); } @@ -415,7 +372,6 @@ export class SqliteFS { * Returns array of {path, data} */ exportGitObjects(): Array<{ path: string; data: Uint8Array }> { - console.log('[Git FS] Exporting git objects...'); const objects = this.sql<{ path: string; data: string; is_dir: number }>` SELECT path, data, is_dir FROM git_objects WHERE path LIKE '.git/%' `; @@ -433,12 +389,11 @@ export class SqliteFS { } exported.push({ - path: '/' + obj.path, // Add leading slash for consistency + path: obj.path, data: bytes }); } - console.log(`[Git FS] Exported ${exported.length} git objects`); return exported; } } \ No newline at end of file diff --git a/worker/agents/git/git-clone-service.ts b/worker/agents/git/git-clone-service.ts index 5a4b92a0..175d864e 100644 --- a/worker/agents/git/git-clone-service.ts +++ b/worker/agents/git/git-clone-service.ts @@ -14,6 +14,7 @@ export interface RepositoryBuildOptions { gitObjects: Array<{ path: string; data: Uint8Array }>; templateDetails: SandboxTemplateDetails | null | undefined; appQuery: string; + appCreatedAt?: Date; // App creation timestamp for deterministic template commit } export class GitCloneService { @@ -28,75 +29,170 @@ export class GitCloneService { * Result: Template base + agent's commit history on top */ static async buildRepository(options: RepositoryBuildOptions): Promise { - const { gitObjects, templateDetails, appQuery } = options; + const { gitObjects, templateDetails, appQuery, appCreatedAt } = options; const fs = new MemFS(); try { - logger.info('Building git repository with template rebasing', { + logger.info('Building git repository with template rebase', { templateName: templateDetails?.name, - templateFileCount: templateDetails ? Object.keys(templateDetails.allFiles).length : 0, gitObjectCount: gitObjects.length }); - // Step 1: Create base commit with template files + // If no agent commits yet, just create template base + if (gitObjects.length === 0) { + await git.init({ fs, dir: '/', defaultBranch: 'main' }); + + if (templateDetails && templateDetails.allFiles) { + // Create template commit + for (const [path, content] of Object.entries(templateDetails.allFiles)) { + await fs.writeFile(path, content as string); + await git.add({ fs, dir: '/', filepath: path }); + } + await git.commit({ + fs, dir: '/', + message: `Template: ${templateDetails.name}`, + author: { + name: 'Vibesdk', + email: 'template@vibesdk.com', + timestamp: appCreatedAt ? Math.floor(appCreatedAt.getTime() / 1000) : 0 + } + }); + } + + return fs; + } + await git.init({ fs, dir: '/', defaultBranch: 'main' }); - if (templateDetails?.allFiles) { - // Write template files + // Create template base commit + let baseCommit: string | null = null; + if (templateDetails && templateDetails.allFiles && Object.keys(templateDetails.allFiles).length > 0) { + logger.info('Creating template base', { + templateName: templateDetails.name, + fileCount: Object.keys(templateDetails.allFiles).length + }); + for (const [path, content] of Object.entries(templateDetails.allFiles)) { - fs.writeFile(path, content); + await fs.writeFile(path, content as string); + await git.add({ fs, dir: '/', filepath: path }); } - // Stage and commit template files - await git.add({ fs, dir: '/', filepath: '.' }); - const templateCommitOid = await git.commit({ - fs, - dir: '/', - message: `Template: ${templateDetails.name}\n\nBase template for Vibesdk application\nQuery: ${appQuery}`, - author: { - name: 'Vibesdk Agent', - email: 'vibesdk-bot@cloudflare.dev', - timestamp: Math.floor(Date.now() / 1000) + baseCommit = await git.commit({ + fs, dir: '/', + message: `Template: ${templateDetails.name}\n\nBase template for ${appQuery}`, + author: { + name: 'Vibesdk', + email: 'template@vibesdk.com', + timestamp: appCreatedAt ? Math.floor(appCreatedAt.getTime() / 1000) : 0 } }); - logger.info('Created template base commit', { oid: templateCommitOid }); + logger.info('Template base created', { baseCommit }); } - // Step 2: Import exported git objects from agent - if (gitObjects.length > 0) { - logger.info('Importing git objects from agent', { count: gitObjects.length }); - - for (const obj of gitObjects) { - fs.writeFile(obj.path, obj.data); + // Find agent's HEAD from exported objects + const headFile = gitObjects.find(obj => obj.path === '.git/HEAD'); + const headContent = headFile ? new TextDecoder().decode(headFile.data).trim() : null; + + let agentHeadOid: string | null = null; + if (headContent && headContent.startsWith('ref: ')) { + // HEAD is a ref, resolve it + const refPath = headContent.slice(5).trim(); + const refFile = gitObjects.find(obj => obj.path === `.git/${refPath}`); + agentHeadOid = refFile ? new TextDecoder().decode(refFile.data).trim() : null; + } else if (headContent && headContent.length === 40) { + // HEAD is direct SHA + agentHeadOid = headContent; + } + + if (!agentHeadOid) { + throw new Error('Could not determine agent HEAD from exported objects'); + } + + logger.info('Found agent original HEAD', { agentHeadOid }); + + // Import git objects (skip refs to preserve template base) + logger.info('Importing agent git objects', { count: gitObjects.length }); + let importedCount = 0; + for (const obj of gitObjects) { + if (obj.path.startsWith('.git/refs/') || obj.path === '.git/HEAD' || obj.path === '.git/packed-refs') { + continue; } + await fs.writeFile(obj.path, obj.data); + importedCount++; + } + logger.info('Imported git objects (excluding refs)', { importedCount }); + + // Get agent's commit history (oldest to newest) + const agentLog = await git.log({ fs, dir: '/', ref: agentHeadOid }); + const commitsOldestFirst = agentLog.reverse(); + + logger.info('Replaying agent commits on template base', { + commitCount: commitsOldestFirst.length, + hasTemplate: !!baseCommit + }); + + // Replay agent commits on top of template base + for (const commitInfo of commitsOldestFirst) { + // Collect files from agent's commit tree + const agentFiles: Array<{ path: string; oid: string }> = []; - // Step 3: Get agent's HEAD and update main branch - try { - const agentHeadOid = await git.resolveRef({ fs, dir: '/', ref: 'HEAD' }); - - // Update main branch to point to agent's HEAD - // This rebases agent's commits on top of template - await git.writeRef({ - fs, - dir: '/', - ref: 'refs/heads/main', - value: agentHeadOid, - force: true - }); + const walkTree = async (treeOid: string, basePath: string = '') => { + const { tree } = await git.readTree({ fs, dir: '/', oid: treeOid }); - logger.info('Rebased agent history on template', { - agentHead: agentHeadOid - }); - } catch (error) { - logger.warn('Could not rebase agent history', { error }); - // Template commit is already in place, continue + for (const entry of tree) { + const fullPath = basePath ? `${basePath}/${entry.path}` : entry.path; + + if (entry.type === 'blob') { + agentFiles.push({ path: fullPath, oid: entry.oid }); + } else if (entry.type === 'tree') { + await walkTree(entry.oid, fullPath); + } + } + }; + + await walkTree(commitInfo.commit.tree); + + // Write agent files + for (const file of agentFiles) { + const { blob } = await git.readBlob({ fs, dir: '/', oid: file.oid }); + await fs.writeFile(file.path, blob); + } + + // Stage all files (template + agent) + const allFiles: string[] = []; + const getAllFiles = async (dir: string): Promise => { + const entries = await fs.readdir(dir); + for (const entry of entries) { + const fullPath = dir === '/' ? entry : `${dir}/${entry}`; + if (entry === '.git' || fullPath === '.git') continue; + + const stat = await fs.stat(fullPath); + if (stat.type === 'dir') { + await getAllFiles(fullPath); + } else { + allFiles.push(fullPath.startsWith('/') ? fullPath.slice(1) : fullPath); + } + } + }; + + await getAllFiles('/'); + + for (const filepath of allFiles) { + await git.add({ fs, dir: '/', filepath }); } - } else { - logger.info('No agent git history found, using template only'); + + // Commit with original metadata + await git.commit({ + fs, + dir: '/', + message: commitInfo.commit.message, + author: commitInfo.commit.author, + committer: commitInfo.commit.committer || commitInfo.commit.author + }); } - logger.info('Git repository built successfully'); + logger.info('Repository built with proper template base and agent commits'); return fs; } catch (error) { logger.error('Failed to build git repository', { error }); @@ -111,8 +207,21 @@ export class GitCloneService { */ static async handleInfoRefs(fs: MemFS): Promise { try { + logger.info('Generating info/refs response'); + const head = await git.resolveRef({ fs, dir: '/', ref: 'HEAD' }); - const branches = await git.listBranches({ fs, dir: '/' }); + logger.info('Resolved HEAD', { head }); + + // Manually list branches from .git/refs/heads/ to avoid git.listBranches() hanging + let branches: string[] = []; + try { + const headsDir = await fs.readdir('.git/refs/heads'); + branches = headsDir.filter((name: string) => !name.startsWith('.')); + logger.info('Found branches', { branches }); + } catch (err) { + logger.warn('No branches found', { error: err }); + branches = []; + } // Git HTTP protocol: info/refs response format let response = '001e# service=git-upload-pack\n0000'; @@ -123,13 +232,19 @@ export class GitCloneService { // Branch refs for (const branch of branches) { - const oid = await git.resolveRef({ fs, dir: '/', ref: `refs/heads/${branch}` }); - response += this.formatPacketLine(`${oid} refs/heads/${branch}\n`); + try { + const oid = await git.resolveRef({ fs, dir: '/', ref: `refs/heads/${branch}` }); + response += this.formatPacketLine(`${oid} refs/heads/${branch}\n`); + logger.info('Added branch ref', { branch, oid }); + } catch (err) { + logger.warn('Failed to resolve branch', { branch, error: err }); + } } // Flush packet response += '0000'; + logger.info('Generated info/refs response', { responseLength: response.length }); return response; } catch (error) { logger.error('Failed to handle info/refs', { error }); @@ -139,66 +254,54 @@ export class GitCloneService { /** * Handle git upload-pack request (actual clone operation) - * Generates and returns packfile for git client + * Includes all git objects to ensure template-rebased commits work correctly */ static async handleUploadPack(fs: MemFS): Promise { try { - const head = await git.resolveRef({ fs, dir: '/', ref: 'HEAD' }); - const objects = new Set(); - - const walkCommits = async (oid: string): Promise => { - if (objects.has(oid)) return; // Already visited - objects.add(oid); - - const { commit } = await git.readCommit({ fs, dir: '/', oid }); - objects.add(commit.tree); - - // Recursively collect all tree and blob objects from this commit - await collectTreeObjects(commit.tree); - - // Walk parent commits recursively - for (const parentOid of commit.parent) { - await walkCommits(parentOid); - } - }; + // Collect all git objects from filesystem + const allObjects = new Set(); - // Recursively collect all tree and blob objects - const collectTreeObjects = async (treeOid: string): Promise => { - if (objects.has(treeOid)) return; - objects.add(treeOid); - - const { tree } = await git.readTree({ fs, dir: '/', oid: treeOid }); - - for (const entry of tree) { - objects.add(entry.oid); - if (entry.type === 'tree') { - await collectTreeObjects(entry.oid); + const objectDirs = await fs.readdir('.git/objects'); + for (const item of objectDirs) { + if (item.length === 2 && /[0-9a-f]{2}/.test(item)) { + const objectFiles = await fs.readdir(`.git/objects/${item}`); + for (const file of objectFiles) { + if (file.length === 38 && /[0-9a-f]{38}/.test(file)) { + allObjects.add(item + file); + } } } - }; - - await walkCommits(head); - - logger.info('Generating packfile with full commit history', { - objectCount: objects.size - }); + } - // Create packfile with all objects + logger.info('Generating packfile', { objectCount: allObjects.size }); const packResult = await git.packObjects({ fs, dir: '/', - oids: Array.from(objects) + oids: Array.from(allObjects) }); - // packObjects returns { packfile: Uint8Array } const packfile = packResult.packfile; if (!packfile) { throw new Error('Failed to generate packfile'); } - // Wrap packfile in sideband format for git protocol - return this.wrapInSideband(packfile); + // NAK packet: "0008NAK\n" + const nakPacket = new Uint8Array([ + 0x30, 0x30, 0x30, 0x38, + 0x4e, 0x41, 0x4b, + 0x0a + ]); + + // Wrap packfile in sideband format + const sideband = this.wrapInSideband(packfile); + + // Concatenate NAK + sideband packfile + const result = new Uint8Array(nakPacket.length + sideband.length); + result.set(nakPacket, 0); + result.set(sideband, nakPacket.length); + + return result; } catch (error) { logger.error('Failed to handle upload-pack', { error }); throw new Error(`Failed to generate pack: ${error instanceof Error ? error.message : String(error)}`); @@ -215,16 +318,39 @@ export class GitCloneService { } /** - * Wrap packfile data in sideband format - * Sideband-64k protocol for multiplexing pack data and progress + * Wrap packfile in sideband-64k format */ private static wrapInSideband(packfile: Uint8Array): Uint8Array { - // Simple implementation: send packfile in one sideband message - // Channel 1 = pack data - const header = new Uint8Array([1]); // Sideband channel 1 - const result = new Uint8Array(header.length + packfile.length); - result.set(header, 0); - result.set(packfile, header.length); + const CHUNK_SIZE = 65515; + const chunks: Uint8Array[] = []; + let offset = 0; + while (offset < packfile.length) { + const chunkSize = Math.min(CHUNK_SIZE, packfile.length - offset); + const chunk = packfile.slice(offset, offset + chunkSize); + + const packetLength = 4 + 1 + chunkSize; + const lengthHex = packetLength.toString(16).padStart(4, '0'); + const packet = new Uint8Array(4 + 1 + chunkSize); + for (let i = 0; i < 4; i++) { + packet[i] = lengthHex.charCodeAt(i); + } + packet[4] = 0x01; + packet.set(chunk, 5); + + chunks.push(packet); + offset += chunkSize; + } + + const flush = new Uint8Array([0x30, 0x30, 0x30, 0x30]); + chunks.push(flush); + const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + const result = new Uint8Array(totalLength); + let resultOffset = 0; + for (const chunk of chunks) { + result.set(chunk, resultOffset); + resultOffset += chunk.length; + } + return result; } } diff --git a/worker/agents/git/git.ts b/worker/agents/git/git.ts index 0f89d974..42367654 100644 --- a/worker/agents/git/git.ts +++ b/worker/agents/git/git.ts @@ -157,8 +157,23 @@ export class GitVersionControl { async getHead(): Promise { try { - return await git.resolveRef({ fs: this.fs, dir: '/', ref: 'HEAD' }); - } catch { + console.log('[Git] getHead: Starting git.resolveRef...'); + + // Add timeout to detect hangs + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => { + console.error('[Git] getHead: TIMEOUT after 5 seconds - git.resolveRef is hanging!'); + reject(new Error('git.resolveRef timed out after 5 seconds')); + }, 5000); + }); + + const resolvePromise = git.resolveRef({ fs: this.fs, dir: '/', ref: 'HEAD' }); + + const result = await Promise.race([resolvePromise, timeoutPromise]); + console.log('[Git] getHead: Resolved to', result); + return result; + } catch (error) { + console.log('[Git] getHead: Error or timeout:', error); return null; } } diff --git a/worker/agents/git/memfs.ts b/worker/agents/git/memfs.ts index b5b0fdab..8e63642b 100644 --- a/worker/agents/git/memfs.ts +++ b/worker/agents/git/memfs.ts @@ -1,28 +1,31 @@ /** * In-memory filesystem for git clone operations - * Minimal implementation for isomorphic-git compatibility + * Full async implementation for isomorphic-git compatibility */ export class MemFS { private files = new Map(); - /** - * Write file to memory - */ - writeFile(path: string, data: string | Uint8Array): void { + constructor() { + // promises property required for isomorphic-git + Object.defineProperty(this, 'promises', { + value: this, + enumerable: true, + writable: false, + configurable: false + }); + } + + async writeFile(path: string, data: string | Uint8Array): Promise { const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data; - // Normalize path (remove leading slash for consistency) const normalized = path.startsWith('/') ? path.slice(1) : path; this.files.set(normalized, bytes); } - /** - * Read file from memory - */ - readFile(path: string, options?: { encoding?: 'utf8' }): Uint8Array | string { + async readFile(path: string, options?: { encoding?: 'utf8' | string }): Promise { const normalized = path.startsWith('/') ? path.slice(1) : path; const data = this.files.get(normalized); @@ -39,10 +42,7 @@ export class MemFS { return data; } - /** - * List directory contents - */ - readdir(dirPath: string): string[] { + async readdir(dirPath: string): Promise { const normalized = dirPath === '/' ? '' : (dirPath.startsWith('/') ? dirPath.slice(1) : dirPath); const prefix = normalized ? normalized + '/' : ''; const results = new Set(); @@ -60,10 +60,22 @@ export class MemFS { return Array.from(results); } - /** - * Get file/directory stats - */ - stat(path: string) { + async stat(path: string): Promise<{ + type: 'file' | 'dir'; + mode: number; + size: number; + mtimeMs: number; + ino: number; + uid: number; + gid: number; + dev: number; + ctime: Date; + mtime: Date; + ctimeMs: number; + isFile: () => boolean; + isDirectory: () => boolean; + isSymbolicLink: () => boolean; + }> { const normalized = path.startsWith('/') ? path.slice(1) : path; // Check if it's a file @@ -76,7 +88,14 @@ export class MemFS { mtimeMs: Date.now(), ino: 0, uid: 0, - gid: 0 + gid: 0, + dev: 0, + ctime: new Date(), + mtime: new Date(), + ctimeMs: Date.now(), + isFile: () => true, + isDirectory: () => false, + isSymbolicLink: () => false }; } @@ -91,7 +110,14 @@ export class MemFS { mtimeMs: Date.now(), ino: 0, uid: 0, - gid: 0 + gid: 0, + dev: 0, + ctime: new Date(), + mtime: new Date(), + ctimeMs: Date.now(), + isFile: () => false, + isDirectory: () => true, + isSymbolicLink: () => false }; } } @@ -101,44 +127,43 @@ export class MemFS { throw error; } - /** - * Lstat (same as stat for in-memory fs) - */ - lstat(path: string) { + async lstat(path: string) { return this.stat(path); } - /** - * Create directory (no-op for in-memory fs) - */ - mkdir(): void { - // No-op: directories are implicit in path structure + async mkdir(_path: string, _options?: any): Promise { + // No-op: directories implicit in paths } - /** - * Remove directory (no-op for in-memory fs) - */ - rmdir(): void { + async rmdir(_path: string): Promise { // No-op } - /** - * Delete file - */ - unlink(path: string): void { - const normalized = path.startsWith('/') ? path.slice(1) : path; - this.files.delete(normalized); + async rename(oldPath: string, newPath: string): Promise { + const oldNormalized = oldPath.startsWith('/') ? oldPath.slice(1) : oldPath; + const newNormalized = newPath.startsWith('/') ? newPath.slice(1) : newPath; + + const data = this.files.get(oldNormalized); + if (data) { + this.files.set(newNormalized, data); + this.files.delete(oldNormalized); + } } - /** - * Check if path exists - */ - exists(path: string): boolean { - try { - this.stat(path); - return true; - } catch { - return false; - } + async chmod(_path: string, _mode: number): Promise { + // No-op + } + + async readlink(_path: string): Promise { + throw new Error('Symbolic links not supported in MemFS'); + } + + async symlink(_target: string, _path: string): Promise { + throw new Error('Symbolic links not supported in MemFS'); + } + + async unlink(path: string): Promise { + const normalized = path.startsWith('/') ? path.slice(1) : path; + this.files.delete(normalized); } } diff --git a/worker/agents/services/implementations/FileManager.ts b/worker/agents/services/implementations/FileManager.ts index 8e174d48..02c6940a 100644 --- a/worker/agents/services/implementations/FileManager.ts +++ b/worker/agents/services/implementations/FileManager.ts @@ -59,10 +59,6 @@ export class FileManager implements IFileManager { if (oldFileContents !== file.fileContents) { try { lastDiff = Diff.createPatch(file.filePath, oldFileContents, file.fileContents); - if (lastDiff) { - const isNewFile = oldFileContents === ''; - console.log(`Generated diff for ${isNewFile ? 'new' : ''} file ${file.filePath}:`, lastDiff); - } } catch (error) { console.error(`Failed to generate diff for file ${file.filePath}:`, error); } diff --git a/worker/api/handlers/git-protocol.ts b/worker/api/handlers/git-protocol.ts index 7c05b240..f80ae1f7 100644 --- a/worker/api/handlers/git-protocol.ts +++ b/worker/api/handlers/git-protocol.ts @@ -46,17 +46,21 @@ async function verifyGitAccess( request: Request, env: Env, appId: string -): Promise { +): Promise<{ hasAccess: boolean; appCreatedAt?: Date }> { + logger.info('Verifying git access', { appId }); const appService = new AppService(env); const app = await appService.getAppDetails(appId); + logger.info('App details retrieved', { appId, found: !!app, visibility: app?.visibility }); + if (!app) { - return false; + logger.warn('App not found in database', { appId }); + return { hasAccess: false }; } // Public apps: anyone can clone if (app.visibility === 'public') { - return true; + return { hasAccess: true, appCreatedAt: app.createdAt || undefined }; } // Private apps: require authentication @@ -73,7 +77,7 @@ async function verifyGitAccess( } if (!token) { - return false; + return { hasAccess: false }; } // Verify token using JWTUtils @@ -81,11 +85,12 @@ async function verifyGitAccess( const payload = await jwtUtils.verifyToken(token); if (!payload) { - return false; + return { hasAccess: false }; } // Check if user owns the app - return payload.sub === app.userId; + const hasAccess = payload.sub === app.userId; + return { hasAccess, appCreatedAt: hasAccess ? (app.createdAt || undefined) : undefined }; } /** @@ -94,7 +99,7 @@ async function verifyGitAccess( async function handleInfoRefs(request: Request, env: Env, appId: string): Promise { try { // Verify access first - const hasAccess = await verifyGitAccess(request, env, appId); + const { hasAccess, appCreatedAt } = await verifyGitAccess(request, env, appId); if (!hasAccess) { return new Response('Repository not found', { status: 404 }); } @@ -122,7 +127,8 @@ async function handleInfoRefs(request: Request, env: Env, appId: string): Promis const repoFS = await GitCloneService.buildRepository({ gitObjects, templateDetails, - appQuery: query + appQuery: query, + appCreatedAt }); // Generate info/refs response @@ -146,7 +152,7 @@ async function handleInfoRefs(request: Request, env: Env, appId: string): Promis async function handleUploadPack(request: Request, env: Env, appId: string): Promise { try { // Verify access first - const hasAccess = await verifyGitAccess(request, env, appId); + const { hasAccess, appCreatedAt } = await verifyGitAccess(request, env, appId); if (!hasAccess) { return new Response('Repository not found', { status: 404 }); } @@ -167,7 +173,8 @@ async function handleUploadPack(request: Request, env: Env, appId: string): Prom const repoFS = await GitCloneService.buildRepository({ gitObjects, templateDetails, - appQuery: query + appQuery: query, + appCreatedAt }); // Generate packfile with full commit history From 14caff981d788b44a3ffd00850cf0944fe657f30 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sat, 25 Oct 2025 19:01:43 -0400 Subject: [PATCH 080/150] chore: bump @ashishkumar472/cf-git from 1.0.3 to 1.0.4 --- bun.lock | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index 84f7c027..926052df 100644 --- a/bun.lock +++ b/bun.lock @@ -4,7 +4,7 @@ "": { "name": "vibesdk", "dependencies": { - "@ashishkumar472/cf-git": "1.0.3", + "@ashishkumar472/cf-git": "^1.0.4", "@cloudflare/containers": "^0.0.28", "@cloudflare/sandbox": "0.4.7", "@noble/ciphers": "^1.3.0", @@ -142,7 +142,7 @@ "@apidevtools/json-schema-ref-parser": ["@apidevtools/json-schema-ref-parser@11.9.3", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" } }, "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ=="], - "@ashishkumar472/cf-git": ["@ashishkumar472/cf-git@1.0.3", "", { "dependencies": { "clean-git-ref": "^2.0.1", "crc-32": "^1.2.0", "diff3": "0.0.3", "ignore": "^5.1.4", "is-git-ref-name-valid": "^1.0.0", "minimisted": "^2.0.0", "pako": "^1.0.10", "pify": "^4.0.1", "readable-stream": "^3.4.0", "simple-get": "^4.0.1" }, "bin": { "isogit": "cli.cjs" } }, "sha512-f6/r6OhZf31Wk8zm81ikmMKn+D4+EsacH6ym+WlE+GG2j5UTMDupuqvRaZMKWKkSYFqd6O5G19ROy+muUZLabA=="], + "@ashishkumar472/cf-git": ["@ashishkumar472/cf-git@1.0.4", "", { "dependencies": { "clean-git-ref": "^2.0.1", "crc-32": "^1.2.0", "diff3": "0.0.3", "ignore": "^5.1.4", "is-git-ref-name-valid": "^1.0.0", "minimisted": "^2.0.0", "pako": "^1.0.10", "pify": "^4.0.1", "readable-stream": "^3.4.0", "simple-get": "^4.0.1" }, "bin": { "isogit": "cli.cjs" } }, "sha512-ss0BXxS51VdA1fbU5xS6F+DffaCss7qlRwkAITHW3kyYj/yuM/W31gyO9tsb4RmpCk/dmf8a+8jR1NDZd7O70Q=="], "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], diff --git a/package.json b/package.json index f49dfafa..76e8b48c 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "knip:exports": "knip --exports" }, "dependencies": { - "@ashishkumar472/cf-git": "1.0.3", + "@ashishkumar472/cf-git": "1.0.4", "@cloudflare/containers": "^0.0.28", "@cloudflare/sandbox": "0.4.7", "@noble/ciphers": "^1.3.0", From 263ac37b3d0289f5b0ca3c1eda8273a001088321 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sat, 25 Oct 2025 19:14:26 -0400 Subject: [PATCH 081/150] feat: add in-memory caching for git clone operations with 5s TTL --- worker/api/handlers/git-cache.ts | 145 ++++++++++++++++++++++++++++ worker/api/handlers/git-protocol.ts | 109 +++++++++++++++++++-- worker/index.ts | 2 +- 3 files changed, 247 insertions(+), 9 deletions(-) create mode 100644 worker/api/handlers/git-cache.ts diff --git a/worker/api/handlers/git-cache.ts b/worker/api/handlers/git-cache.ts new file mode 100644 index 00000000..bba34c99 --- /dev/null +++ b/worker/api/handlers/git-cache.ts @@ -0,0 +1,145 @@ +import { createHash } from 'crypto'; +import type { MemFS } from '../../agents/git/memfs'; +import type { TemplateDetails as SandboxTemplateDetails } from '../../services/sandbox/sandboxTypes'; + +interface GitCacheMetadata { + agentHeadOid: string; + templateHash: string; + timestamp: number; +} + +interface InMemoryCacheEntry { + repo: MemFS; + metadata: GitCacheMetadata; + timestamp: number; +} + +// Worker-global in-memory cache (survives across requests in same Worker instance) +const inMemoryCache = new Map(); + +// Cleanup tracker +let lastCleanup = Date.now(); + +function cleanupMemoryCache(): void { + const now = Date.now(); + if (now - lastCleanup < 10000) return; // Only cleanup once per 10 seconds + + lastCleanup = now; + const cutoff = now - 10000; // Remove entries older than 10s + + for (const [key, entry] of inMemoryCache.entries()) { + if (entry.timestamp < cutoff) { + inMemoryCache.delete(key); + } + } +} + +export class GitCache { + constructor() {} + + private getTemplateHash(templateDetails: SandboxTemplateDetails | null | undefined): string { + if (!templateDetails) return 'no-template'; + + const content = JSON.stringify({ + name: templateDetails.name, + files: Object.keys(templateDetails.allFiles || {}).sort() + }); + + return createHash('sha256').update(content).digest('hex').slice(0, 16); + } + + private getCacheKey(appId: string): string { + return `git-repo:${appId}`; + } + + /** + * Check if in-memory cache is valid and fresh (< 5 seconds old) + */ + private checkMemoryCache( + appId: string, + currentHeadOid: string, + currentTemplateDetails: SandboxTemplateDetails | null | undefined + ): MemFS | null { + cleanupMemoryCache(); + + const key = this.getCacheKey(appId); + const entry = inMemoryCache.get(key); + + if (!entry) return null; + + // Check if too old (5 second TTL) + if (Date.now() - entry.timestamp > 5000) { + inMemoryCache.delete(key); + return null; + } + + // Check if still valid (OID + template hash match) + const currentTemplateHash = this.getTemplateHash(currentTemplateDetails); + if (entry.metadata.agentHeadOid !== currentHeadOid || + entry.metadata.templateHash !== currentTemplateHash) { + inMemoryCache.delete(key); + return null; + } + + return entry.repo; + } + + /** + * Store repository in memory cache + */ + private storeInMemory( + appId: string, + repo: MemFS, + agentHeadOid: string, + templateDetails: SandboxTemplateDetails | null | undefined + ): void { + const key = this.getCacheKey(appId); + + inMemoryCache.set(key, { + repo, + metadata: { + agentHeadOid, + templateHash: this.getTemplateHash(templateDetails), + timestamp: Date.now() + }, + timestamp: Date.now() + }); + } + + /** + * Get repository from in-memory cache + */ + async getRepository( + appId: string, + agentHeadOid: string, + templateDetails: SandboxTemplateDetails | null | undefined + ): Promise<{ repo: MemFS | null; source: 'memory' | 'miss' }> { + const memoryRepo = this.checkMemoryCache(appId, agentHeadOid, templateDetails); + if (memoryRepo) { + return { repo: memoryRepo, source: 'memory' }; + } + + return { repo: null, source: 'miss' }; + } + + /** + * Store repository in memory cache + */ + storeRepository( + appId: string, + repo: MemFS, + agentHeadOid: string, + templateDetails: SandboxTemplateDetails | null | undefined + ): void { + // Store in memory immediately (synchronous, no await needed) + this.storeInMemory(appId, repo, agentHeadOid, templateDetails); + } + + /** + * Clear cache for an app + */ + clearCache(appId: string): void { + const key = this.getCacheKey(appId); + inMemoryCache.delete(key); + } +} diff --git a/worker/api/handlers/git-protocol.ts b/worker/api/handlers/git-protocol.ts index f80ae1f7..4135ced5 100644 --- a/worker/api/handlers/git-protocol.ts +++ b/worker/api/handlers/git-protocol.ts @@ -10,6 +10,7 @@ import { createLogger } from '../../logger'; import { GitCloneService } from '../../agents/git/git-clone-service'; import { AppService } from '../../database/services/AppService'; import { JWTUtils } from '../../utils/jwtUtils'; +import { GitCache } from './git-cache'; const logger = createLogger('GitProtocol'); @@ -39,6 +40,24 @@ function extractAppId(pathname: string): string | null { return null; } +/** + * Extract agent HEAD OID from git objects + */ +function extractAgentHeadOid(gitObjects: Array<{ path: string; data: Uint8Array }>): string | null { + const headFile = gitObjects.find(obj => obj.path === '.git/HEAD'); + if (!headFile) return null; + + const headContent = new TextDecoder().decode(headFile.data).trim(); + + if (headContent.startsWith('ref: ')) { + const refPath = headContent.slice(5).trim(); + const refFile = gitObjects.find(obj => obj.path === `.git/${refPath}`); + return refFile ? new TextDecoder().decode(refFile.data).trim() : null; + } + + return null; +} + /** * Verify git access (public apps or owner with valid token) */ @@ -96,7 +115,12 @@ async function verifyGitAccess( /** * Handle Git info/refs request */ -async function handleInfoRefs(request: Request, env: Env, appId: string): Promise { +async function handleInfoRefs( + request: Request, + env: Env, + ctx: ExecutionContext, + appId: string +): Promise { try { // Verify access first const { hasAccess, appCreatedAt } = await verifyGitAccess(request, env, appId); @@ -123,6 +147,31 @@ async function handleInfoRefs(request: Request, env: Env, appId: string): Promis }); } + // Extract HEAD OID for cache validation + const agentHeadOid = extractAgentHeadOid(gitObjects); + if (!agentHeadOid) { + throw new Error('Could not determine agent HEAD OID'); + } + + // Try memory cache first + const cache = new GitCache(); + const { repo } = await cache.getRepository(appId, agentHeadOid, templateDetails); + + if (repo) { + logger.info('Cache HIT (memory): info/refs', { appId, agentHeadOid }); + const response = await GitCloneService.handleInfoRefs(repo); + return new Response(response, { + status: 200, + headers: { + 'Content-Type': 'application/x-git-upload-pack-advertisement', + 'Cache-Control': 'no-cache', + 'X-Cache': 'HIT-MEMORY' + } + }); + } + + logger.info('Cache MISS: building repository', { appId, agentHeadOid }); + // Build repository in worker const repoFS = await GitCloneService.buildRepository({ gitObjects, @@ -131,13 +180,22 @@ async function handleInfoRefs(request: Request, env: Env, appId: string): Promis appCreatedAt }); + // Store in memory for subsequent upload-pack request + cache.storeRepository(appId, repoFS, agentHeadOid, templateDetails); + + // Use waitUntil to keep Worker alive for upload-pack request (typically arrives within seconds) + ctx.waitUntil( + new Promise(resolve => setTimeout(resolve, 5000)) + ); + // Generate info/refs response const response = await GitCloneService.handleInfoRefs(repoFS); return new Response(response, { status: 200, headers: { 'Content-Type': 'application/x-git-upload-pack-advertisement', - 'Cache-Control': 'no-cache' + 'Cache-Control': 'no-cache', + 'X-Cache': 'MISS' } }); } catch (error) { @@ -149,7 +207,12 @@ async function handleInfoRefs(request: Request, env: Env, appId: string): Promis /** * Handle Git upload-pack request */ -async function handleUploadPack(request: Request, env: Env, appId: string): Promise { +async function handleUploadPack( + request: Request, + env: Env, + _ctx: ExecutionContext, + appId: string +): Promise { try { // Verify access first const { hasAccess, appCreatedAt } = await verifyGitAccess(request, env, appId); @@ -169,7 +232,32 @@ async function handleUploadPack(request: Request, env: Env, appId: string): Prom return new Response('No commits to pack', { status: 404 }); } - // Build repository in worker + // Extract HEAD OID for cache validation + const agentHeadOid = extractAgentHeadOid(gitObjects); + if (!agentHeadOid) { + throw new Error('Could not determine agent HEAD OID'); + } + + // Try memory cache first (HOT PATH - usually hits after info/refs!) + const cache = new GitCache(); + const { repo, source } = await cache.getRepository(appId, agentHeadOid, templateDetails); + + if (repo) { + logger.info(`Cache HIT (${source}): upload-pack`, { appId, agentHeadOid }); + const packfile = await GitCloneService.handleUploadPack(repo); + return new Response(packfile, { + status: 200, + headers: { + 'Content-Type': 'application/x-git-upload-pack-result', + 'Cache-Control': 'no-cache', + 'X-Cache': `HIT-${source.toUpperCase()}` + } + }); + } + + logger.info('Cache MISS: building repository', { appId, agentHeadOid }); + + // Build repository in worker (cold path - different Worker or timeout) const repoFS = await GitCloneService.buildRepository({ gitObjects, templateDetails, @@ -177,13 +265,17 @@ async function handleUploadPack(request: Request, env: Env, appId: string): Prom appCreatedAt }); + // Store in memory for potential retries + cache.storeRepository(appId, repoFS, agentHeadOid, templateDetails); + // Generate packfile with full commit history const packfile = await GitCloneService.handleUploadPack(repoFS); return new Response(packfile, { status: 200, headers: { 'Content-Type': 'application/x-git-upload-pack-result', - 'Cache-Control': 'no-cache' + 'Cache-Control': 'no-cache', + 'X-Cache': 'MISS' } }); } catch (error) { @@ -197,7 +289,8 @@ async function handleUploadPack(request: Request, env: Env, appId: string): Prom */ export async function handleGitProtocolRequest( request: Request, - env: Env + env: Env, + ctx: ExecutionContext ): Promise { const url = new URL(request.url); const pathname = url.pathname; @@ -210,9 +303,9 @@ export async function handleGitProtocolRequest( // Route to appropriate handler if (GIT_INFO_REFS_PATTERN.test(pathname)) { - return handleInfoRefs(request, env, appId); + return handleInfoRefs(request, env, ctx, appId); } else if (GIT_UPLOAD_PACK_PATTERN.test(pathname)) { - return handleUploadPack(request, env, appId); + return handleUploadPack(request, env, ctx, appId); } return new Response('Not found', { status: 404 }); diff --git a/worker/index.ts b/worker/index.ts index a8e7872d..76171930 100644 --- a/worker/index.ts +++ b/worker/index.ts @@ -144,7 +144,7 @@ const worker = { // Handle Git protocol endpoints directly // Route: /apps/:id.git/info/refs or /apps/:id.git/git-upload-pack if (isGitProtocolRequest(pathname)) { - return handleGitProtocolRequest(request, env); + return handleGitProtocolRequest(request, env, ctx); } // Serve static assets for all non-API routes from the ASSETS binding. From 7f33d02d4b9cf3408f546fe4efd46b9038b79297 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Singh Date: Sun, 26 Oct 2025 01:31:57 -0400 Subject: [PATCH 082/150] feat: reworked github flow, pure DO based + cache token to avoid oauth --- src/components/github-export-modal.tsx | 627 +++++++++++---- src/hooks/use-github-export.ts | 87 ++- src/lib/api-client.ts | 34 +- src/routes/chat/chat.tsx | 7 +- worker/agents/core/simpleGeneratorAgent.ts | 242 ++++-- worker/agents/git/git.ts | 20 +- .../implementations/DeploymentManager.ts | 45 +- .../services/interfaces/IDeploymentManager.ts | 4 - .../controllers/githubExporter/controller.ts | 342 ++++++-- worker/api/routes/githubExporterRoutes.ts | 3 + worker/database/services/AppService.ts | 1 + worker/services/github/GitHubService.ts | 732 +++++++++--------- worker/services/github/types.ts | 2 + worker/services/sandbox/BaseSandboxService.ts | 7 - worker/services/sandbox/sandboxSdkClient.ts | 270 +------ 15 files changed, 1414 insertions(+), 1009 deletions(-) diff --git a/src/components/github-export-modal.tsx b/src/components/github-export-modal.tsx index 7ce6edff..747f8e06 100644 --- a/src/components/github-export-modal.tsx +++ b/src/components/github-export-modal.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { X, @@ -10,6 +10,40 @@ import { AlertCircle, Loader } from 'lucide-react'; +import { apiClient } from '@/lib/api-client'; + +// Shared button styles +const BUTTON_STYLES = { + primary: 'bg-brand hover:bg-brand/90 text-text-on-brand py-2 px-4 rounded-lg transition-colors', + secondary: 'bg-bg-2 hover:bg-border text-text-primary py-2 px-4 rounded-lg transition-colors', + ghost: 'bg-transparent hover:bg-bg-2 text-text-primary/60 hover:text-text-primary py-2 px-4 rounded-lg transition-colors', + warning: 'bg-yellow-500 hover:bg-yellow-600 text-white py-2 px-4 rounded-lg font-medium transition-colors', + danger: 'bg-red-500 hover:bg-red-600 text-white py-2 px-4 rounded-lg transition-colors' +} as const; + +// Helper functions +const getInitialMode = (existingUrl: string | null | undefined): 'first_export' | 'sync' => + existingUrl ? 'sync' : 'first_export'; + +const extractRepoName = (url: string): string => url.split('/').pop() || ''; + +const generateRepoName = (appTitle?: string): string => { + if (appTitle) { + return appTitle + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + .slice(0, 100) || 'my-app'; + } + const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:]/g, '').replace('T', '-'); + return `generated-app-${timestamp}`; +}; + +const createExportOptions = (repositoryName: string, isPrivate: boolean, description: string) => ({ + repositoryName: repositoryName.trim(), + isPrivate, + description: description.trim() || undefined +}); interface GitHubExportModalProps { isOpen: boolean; @@ -29,10 +63,80 @@ interface GitHubExportModalProps { success: boolean; repositoryUrl?: string; error?: string; + repositoryAlreadyExists?: boolean; + existingRepositoryUrl?: string; }; onRetry?: () => void; + existingGithubUrl?: string | null; + agentId?: string; + appTitle?: string; } +// Sub-components +const ModalHeader: React.FC<{ mode: string; existingUrl?: string | null; onClose: () => void; disabled?: boolean }> = + ({ mode, existingUrl, onClose, disabled }) => ( +
+
+
+ +
+
+

+ {mode === 'sync' ? 'Sync to GitHub' : 'Export to GitHub'} +

+

+ {mode === 'sync' && existingUrl + ? `Update ${extractRepoName(existingUrl)} with your latest changes` + : 'Create a new repository with your generated code' + } +

+
+
+ {!disabled && ( + + )} +
+); + +const StatusMessage: React.FC<{ icon: React.ComponentType; iconColor: string; title: string; message: string; children?: React.ReactNode }> = + ({ icon: Icon, iconColor, title, message, children }) => ( +
+ +

{title}

+

{message}

+ {children} +
+); + +const ProgressStep: React.FC<{ label: string; isActive: boolean; isComplete: boolean }> = + ({ label, isActive, isComplete }) => ( +
+
+ {label} +
+); + +const StatusIndicator: React.FC<{ type: 'success' | 'warning' | 'error'; message: string }> = + ({ type, message }) => { + const colors = { + success: 'text-green-500', + warning: 'text-yellow-500', + error: 'text-red-500' + }; + const Icon = type === 'success' ? CheckCircle : AlertCircle; + + return ( +
+ + {message} +
+ ); +}; + export function GitHubExportModal({ isOpen, onClose, @@ -40,40 +144,112 @@ export function GitHubExportModal({ isExporting = false, exportProgress, exportResult, - onRetry + onRetry, + existingGithubUrl, + agentId, + appTitle }: GitHubExportModalProps) { const [repositoryName, setRepositoryName] = useState(''); const [description, setDescription] = useState(''); const [isPrivate, setIsPrivate] = useState(false); + const [mode, setMode] = useState<'first_export' | 'sync' | 'change_repo'>( + getInitialMode(existingGithubUrl) + ); + const [remoteStatus, setRemoteStatus] = useState<{ + compatible: boolean; + behindBy: number; + aheadBy: number; + divergedCommits: Array<{ sha: string; message: string; author: string; date: string }>; + } | null>(null); + const [showConflictWarning, setShowConflictWarning] = useState(false); + const [isCheckingRemote, setIsCheckingRemote] = useState(false); + const [lastExportSuccess, setLastExportSuccess] = useState<{ + repositoryUrl: string; + timestamp: number; + } | null>(null); + + React.useEffect(() => { + if (isOpen) { + setMode(getInitialMode(existingGithubUrl)); + setRepositoryName(''); + } + }, [isOpen, existingGithubUrl, agentId]); + + React.useEffect(() => { + if (exportResult?.success && exportResult.repositoryUrl) { + setLastExportSuccess({ + repositoryUrl: exportResult.repositoryUrl, + timestamp: Date.now() + }); + } + }, [exportResult]); + + const exportOptions = useMemo(() => + createExportOptions(repositoryName, isPrivate, description), + [repositoryName, isPrivate, description] + ); + + const handleExport = useCallback(() => { + onExport(exportOptions); + }, [onExport, exportOptions]); const handleSubmit = useCallback((e: React.FormEvent) => { e.preventDefault(); - - if (!repositoryName.trim()) { - return; + if (repositoryName.trim()) { + handleExport(); } - - // Always call onExport - the parent component will handle OAuth flow - onExport({ - repositoryName: repositoryName.trim(), - isPrivate, - description: description.trim() || undefined - }); - }, [repositoryName, description, isPrivate, onExport]); + }, [repositoryName, handleExport]); const handleClose = useCallback(() => { if (!isExporting) { + setMode(getInitialMode(existingGithubUrl)); + setRepositoryName(''); + setDescription(''); + + if (lastExportSuccess && Date.now() - lastExportSuccess.timestamp > 10000) { + setLastExportSuccess(null); + } + onClose(); } - }, [isExporting, onClose]); + }, [isExporting, onClose, existingGithubUrl, lastExportSuccess]); - // Auto-generate repository name based on current timestamp React.useEffect(() => { if (isOpen && !repositoryName) { - const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:]/g, '').replace('T', '-'); - setRepositoryName(`generated-app-${timestamp}`); + setRepositoryName( + existingGithubUrl + ? extractRepoName(existingGithubUrl) + : generateRepoName(appTitle) + ); + } + }, [isOpen, repositoryName, existingGithubUrl, appTitle]); + + React.useEffect(() => { + if (isOpen && mode === 'sync' && existingGithubUrl && agentId) { + setIsCheckingRemote(true); + setShowConflictWarning(false); + setRemoteStatus(null); + + apiClient.checkRemoteStatus({ + repositoryUrl: existingGithubUrl, + agentId + }) + .then(response => { + if (response.success && response.data) { + setRemoteStatus(response.data); + if (response.data.aheadBy > 0) { + setShowConflictWarning(true); + } + } + }) + .catch(error => { + console.error('Failed to check remote status:', error); + }) + .finally(() => { + setIsCheckingRemote(false); + }); } - }, [isOpen, repositoryName]); + }, [isOpen, mode, existingGithubUrl, agentId]); if (!isOpen) return null; @@ -93,74 +269,139 @@ export function GitHubExportModal({ className="bg-bg-4 border border-border-primary rounded-xl max-w-md w-full p-6" onClick={(e) => e.stopPropagation()} > - {/* Header */} -
-
-
- + + + {showConflictWarning && remoteStatus && remoteStatus.aheadBy > 0 ? ( +
+ +

+ Repository Has Different History +

+
+

+ Remote has {remoteStatus.aheadBy} commit(s) not in your app. + Your app has {remoteStatus.behindBy} commit(s) not on GitHub. +

+ + {remoteStatus.divergedCommits.length > 0 && ( +
+

Commits that will be lost:

+
+ {remoteStatus.divergedCommits.slice(0, 5).map((commit, i) => ( +
+
{commit.message}
+
+ {commit.author} • {new Date(commit.date).toLocaleString()} +
+
+ ))} +
+
+ )} + +
+

+ ⚠️ Force pushing will replace GitHub's history with yours. + This action cannot be undone. +

+
-
-

Export to GitHub

-

Create a new repository with your generated code

+ +
+ +
- {!isExporting && ( - - )} -
- - {/* Content */} - {exportResult ? ( - /* Export Result */ -
- {exportResult.success ? ( -
- -

Export Successful!

-

- Your code has been successfully exported to GitHub -

- + + View Repository + + + ) : exportResult.repositoryAlreadyExists && exportResult.existingRepositoryUrl ? ( + + - - View Repository + {exportResult.existingRepositoryUrl} -
- ) : ( -
- -

Export Failed

-

- {exportResult.error || 'An error occurred during export'} +

+ Would you like to sync your changes to this existing repository?

-
- - -
+
+ + +
- )} -
+ + ) : ( + +
+ + +
+
+ ) ) : isExporting && exportProgress ? ( - /* Export Progress */
@@ -168,7 +409,6 @@ export function GitHubExportModal({

{exportProgress.message}

- {/* Progress Bar */}
Progress @@ -184,35 +424,54 @@ export function GitHubExportModal({
- {/* Step Indicators */}
-
30 ? 'text-green-500' : 'text-text-primary/40' - }`}> -
- Creating Repository -
-
70 ? 'text-green-500' : 'text-text-primary/40' - }`}> -
- Uploading Files -
-
90 ? 'text-green-500' : 'text-text-primary/40' - }`}> -
- Finalizing -
+ 30} + /> + 70} + /> + 90} + />
) : ( - /* Export Form */
- {/* Repository Name */} + {mode === 'sync' && lastExportSuccess && !exportResult && ( +
+
+ +
+

+ Last sync successful +

+ + {lastExportSuccess.repositoryUrl} + +
+ +
+
+ )} +
- {/* Description */} -
- -