diff --git a/.emergent/emergent.yml b/.emergent/emergent.yml new file mode 100644 index 00000000..290142db --- /dev/null +++ b/.emergent/emergent.yml @@ -0,0 +1,4 @@ +{ + "job_id": "b62b6167-55f2-427b-b04a-ac699e91707b", + "created_at": "2026-04-19T16:45:27.303669+00:00Z" +} diff --git a/.env.example b/.env.example deleted file mode 100644 index 85bc998a..00000000 --- a/.env.example +++ /dev/null @@ -1,248 +0,0 @@ -# ====================================== -# Environment Variables for bolt.gives -# ====================================== -# Copy this file to .env.local and fill in your API keys -# See README.md for setup instructions - -# ====================================== -# AI PROVIDER API KEYS -# ====================================== - -# Anthropic Claude -# Get your API key from: https://console.anthropic.com/ -ANTHROPIC_API_KEY=your_anthropic_api_key_here - -# Cerebras (High-performance inference) -# Get your API key from: https://cloud.cerebras.ai/settings -CEREBRAS_API_KEY=your_cerebras_api_key_here - -# Fireworks AI (Fast inference with FireAttention engine) -# Get your API key from: https://fireworks.ai/api-keys -FIREWORKS_API_KEY=your_fireworks_api_key_here - -# OpenAI GPT models -# Get your API key from: https://platform.openai.com/api-keys -OPENAI_API_KEY=your_openai_api_key_here - -# GitHub Models (OpenAI models hosted by GitHub) -# Get your Personal Access Token from: https://github.com/settings/tokens -# - Select "Fine-grained tokens" -# - Set repository access to "All repositories" -# - Enable "GitHub Models" permission -GITHUB_API_KEY=github_pat_your_personal_access_token_here - -# Perplexity AI (Search-augmented models) -# Get your API key from: https://www.perplexity.ai/settings/api -PERPLEXITY_API_KEY=your_perplexity_api_key_here - -# DeepSeek -# Get your API key from: https://platform.deepseek.com/api_keys -DEEPSEEK_API_KEY=your_deepseek_api_key_here - -# Google Gemini -# Get your API key from: https://makersuite.google.com/app/apikey -GOOGLE_GENERATIVE_AI_API_KEY=your_google_gemini_api_key_here - -# Cohere -# Get your API key from: https://dashboard.cohere.ai/api-keys -COHERE_API_KEY=your_cohere_api_key_here - -# Groq (Fast inference) -# Get your API key from: https://console.groq.com/keys -GROQ_API_KEY=your_groq_api_key_here - -# Mistral -# Get your API key from: https://console.mistral.ai/api-keys/ -MISTRAL_API_KEY=your_mistral_api_key_here - -# Together AI -# Get your API key from: https://api.together.xyz/settings/api-keys -TOGETHER_API_KEY=your_together_api_key_here - -# X.AI (Elon Musk's company) -# Get your API key from: https://console.x.ai/ -XAI_API_KEY=your_xai_api_key_here - -# Moonshot AI (Kimi models) -# Get your API key from: https://platform.moonshot.ai/console/api-keys -MOONSHOT_API_KEY=your_moonshot_api_key_here - -# Z.AI (GLM models with JWT authentication) -# Get your API key from: https://open.bigmodel.cn/usercenter/apikeys -ZAI_API_KEY=your_zai_api_key_here - -# Hugging Face -# Get your API key from: https://huggingface.co/settings/tokens -HuggingFace_API_KEY=your_huggingface_api_key_here - -# Hyperbolic -# Get your API key from: https://app.hyperbolic.xyz/settings -HYPERBOLIC_API_KEY=your_hyperbolic_api_key_here - -# OpenRouter (Meta routing for multiple providers) -# Get your API key from: https://openrouter.ai/keys -OPEN_ROUTER_API_KEY=your_openrouter_api_key_here - -# Hosted FREE provider (server-side only) -# This powers the built-in FREE provider that is locked to: -# DeepSeek V3.2 -# Do not expose this value in the browser. -FREE_OPENROUTER_API_KEY=your_server_side_openrouter_api_key_here - -# Public self-host URLs (optional, but recommended for VPS installs) -# The installer can populate these automatically when you choose custom domains. -BOLT_APP_PUBLIC_URL= -BOLT_ADMIN_PANEL_PUBLIC_URL= -BOLT_CREATE_TRIAL_PUBLIC_URL= - -# Tenant admin cookie secret (server-side only) -BOLT_TENANT_ADMIN_COOKIE_SECRET= - -# ====================================== -# CUSTOM PROVIDER BASE URLS (Optional) -# ====================================== - -# Ollama (Local models) -# DON'T USE http://localhost:11434 due to IPv6 issues -# USE: http://127.0.0.1:11434 -OLLAMA_API_BASE_URL=http://127.0.0.1:11434 - -# OpenAI-like API (Compatible providers) -OPENAI_LIKE_API_BASE_URL=your_openai_like_base_url_here -OPENAI_LIKE_API_KEY=your_openai_like_api_key_here - -# Together AI Base URL -TOGETHER_API_BASE_URL=your_together_base_url_here - -# Hyperbolic Base URL -HYPERBOLIC_API_BASE_URL=https://api.hyperbolic.xyz/v1/chat/completions - -# LMStudio (Local models) -# Make sure to enable CORS in LMStudio -# DON'T USE http://localhost:1234 due to IPv6 issues -# USE: http://127.0.0.1:1234 -LMSTUDIO_API_BASE_URL=http://127.0.0.1:1234 - -# ====================================== -# CLOUD SERVICES CONFIGURATION -# ====================================== - -# AWS Bedrock Configuration (JSON format) -# Get your credentials from: https://console.aws.amazon.com/iam/home -# Example: {"region": "us-east-1", "accessKeyId": "yourAccessKeyId", "secretAccessKey": "yourSecretAccessKey"} -AWS_BEDROCK_CONFIG=your_aws_bedrock_config_json_here - -# ====================================== -# GITHUB INTEGRATION -# ====================================== - -# GitHub Personal Access Token -# Get from: https://github.com/settings/tokens -# Used for importing/cloning repositories and accessing private repos -VITE_GITHUB_ACCESS_TOKEN=your_github_personal_access_token_here - -# GitHub Token Type ('classic' or 'fine-grained') -VITE_GITHUB_TOKEN_TYPE=classic - -# ====================================== -# GITLAB INTEGRATION -# ====================================== - -# GitLab Personal Access Token -# Get your GitLab Personal Access Token here: -# https://gitlab.com/-/profile/personal_access_tokens -# -# This token is used for: -# 1. Importing/cloning GitLab repositories -# 2. Accessing private projects -# 3. Creating/updating branches -# 4. Creating/updating commits and pushing code -# 5. Creating new GitLab projects via the API -# -# Make sure your token has the following scopes: -# - api (for full API access including project creation and commits) -# - read_repository (to clone/import repositories) -# - write_repository (to push commits and update branches) -VITE_GITLAB_ACCESS_TOKEN=your_gitlab_personal_access_token_here - -# Set the GitLab instance URL (e.g., https://gitlab.com or your self-hosted domain) -VITE_GITLAB_URL=https://gitlab.com - -# GitLab token type should be 'personal-access-token' -VITE_GITLAB_TOKEN_TYPE=personal-access-token - -# ====================================== -# VERCEL INTEGRATION -# ====================================== - -# Vercel Access Token -# Get your access token from: https://vercel.com/account/tokens -# This token is used for: -# 1. Deploying projects to Vercel -# 2. Managing Vercel projects and deployments -# 3. Accessing project analytics and logs -VITE_VERCEL_ACCESS_TOKEN=your_vercel_access_token_here - -# ====================================== -# NETLIFY INTEGRATION -# ====================================== - -# Netlify Access Token -# Get your access token from: https://app.netlify.com/user/applications -# This token is used for: -# 1. Deploying sites to Netlify -# 2. Managing Netlify sites and deployments -# 3. Accessing build logs and analytics -VITE_NETLIFY_ACCESS_TOKEN=your_netlify_access_token_here - -# ====================================== -# SUPABASE INTEGRATION -# ====================================== - -# Supabase Project Configuration -# Get your project details from: https://supabase.com/dashboard -# Select your project → Settings → API -VITE_SUPABASE_URL=your_supabase_project_url_here -VITE_SUPABASE_ANON_KEY=your_supabase_anon_key_here - -# Supabase Access Token (for management operations) -# Generate from: https://supabase.com/dashboard/account/tokens -VITE_SUPABASE_ACCESS_TOKEN=your_supabase_access_token_here - -# ====================================== -# DEVELOPMENT SETTINGS -# ====================================== - -# Development Mode -NODE_ENV=development - -# Application Port (optional, defaults to 5173 for development) -PORT=5173 - -# Logging Level (debug, info, warn, error) -VITE_LOG_LEVEL=debug - -# Default Context Window Size (for local models) -DEFAULT_NUM_CTX=32768 - -# ====================================== -# LOCAL POSTGRESQL FOR SELF-HOSTED ADMIN PANEL (Optional) -# ====================================== -# The installer can provision a local PostgreSQL service and populate these values automatically. -# For local self-hosting, set SSL to disable. -BOLT_ADMIN_DATABASE_HOST= -BOLT_ADMIN_DATABASE_PORT=5432 -BOLT_ADMIN_DATABASE_NAME= -BOLT_ADMIN_DATABASE_USER= -BOLT_ADMIN_DATABASE_PASSWORD= -BOLT_ADMIN_DATABASE_SSL=disable - -# ====================================== -# SETUP INSTRUCTIONS -# ====================================== -# 1. Copy this file to .env.local: cp .env.example .env.local -# 2. Fill in the API keys for the services you want to use -# 3. All service integration keys use VITE_ prefix for auto-connection -# 4. Restart your development server: pnpm run dev -# 5. Services will auto-connect on startup if tokens are provided -# 6. Go to Settings > Service tabs to manage connections manually if needed diff --git a/.env.production b/.env.production deleted file mode 100644 index d66c990d..00000000 --- a/.env.production +++ /dev/null @@ -1,144 +0,0 @@ -# Rename this file to .env once you have filled in the below environment variables! - -# Get your GROQ API Key here - -# https://console.groq.com/keys -# You only need this environment variable set if you want to use Groq models -GROQ_API_KEY= - -# Get your HuggingFace API Key here - -# https://huggingface.co/settings/tokens -# You only need this environment variable set if you want to use HuggingFace models -HuggingFace_API_KEY= - -# Get your Open AI API Key by following these instructions - -# https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key -# You only need this environment variable set if you want to use GPT models -OPENAI_API_KEY= - -# Get your Anthropic API Key in your account settings - -# https://console.anthropic.com/settings/keys -# You only need this environment variable set if you want to use Claude models -ANTHROPIC_API_KEY= - -# Get your OpenRouter API Key in your account settings - -# https://openrouter.ai/settings/keys -# You only need this environment variable set if you want to use OpenRouter models -# OPEN_ROUTER_API_KEY intentionally unset on hosted instances; users supply their own OpenRouter key in the UI - -# Get your Google Generative AI API Key by following these instructions - -# https://console.cloud.google.com/apis/credentials -# You only need this environment variable set if you want to use Google Generative AI models -GOOGLE_GENERATIVE_AI_API_KEY= - -# You only need this environment variable set if you want to use oLLAMA models -# DONT USE http://localhost:11434 due to IPV6 issues -# USE EXAMPLE http://127.0.0.1:11434 -OLLAMA_API_BASE_URL= - -# You only need this environment variable set if you want to use OpenAI Like models -OPENAI_LIKE_API_BASE_URL= - -# You only need this environment variable set if you want to use Together AI models -TOGETHER_API_BASE_URL= - -# You only need this environment variable set if you want to use DeepSeek models through their API -DEEPSEEK_API_KEY= - -# Get your OpenAI Like API Key -OPENAI_LIKE_API_KEY= - -# Get your Together API Key -TOGETHER_API_KEY= - -# You only need this environment variable set if you want to use Hyperbolic models -HYPERBOLIC_API_KEY= -HYPERBOLIC_API_BASE_URL= - -# Get your Mistral API Key by following these instructions - -# https://console.mistral.ai/api-keys/ -# You only need this environment variable set if you want to use Mistral models -MISTRAL_API_KEY= - -# Get the Cohere Api key by following these instructions - -# https://dashboard.cohere.com/api-keys -# You only need this environment variable set if you want to use Cohere models -COHERE_API_KEY= - -# Get LMStudio Base URL from LM Studio Developer Console -# Make sure to enable CORS -# DONT USE http://localhost:1234 due to IPV6 issues -# Example: http://127.0.0.1:1234 -LMSTUDIO_API_BASE_URL= - -# Get your xAI API key -# https://x.ai/api -# You only need this environment variable set if you want to use xAI models -XAI_API_KEY= - -# Get your Perplexity API Key here - -# https://www.perplexity.ai/settings/api -# You only need this environment variable set if you want to use Perplexity models -PERPLEXITY_API_KEY= - -# Get your AWS configuration -# https://console.aws.amazon.com/iam/home -AWS_BEDROCK_CONFIG= - -# Include this environment variable if you want more logging for debugging locally -VITE_LOG_LEVEL= - -# Get your GitHub Personal Access Token here - -# https://github.com/settings/tokens -# This token is used for: -# 1. Importing/cloning GitHub repositories without rate limiting -# 2. Accessing private repositories -# 3. Automatic GitHub authentication (no need to manually connect in the UI) -# -# For classic tokens, ensure it has these scopes: repo, read:org, read:user -# For fine-grained tokens, ensure it has Repository and Organization access -VITE_GITHUB_ACCESS_TOKEN= - -# Specify the type of GitHub token you're using -# Can be 'classic' or 'fine-grained' -# Classic tokens are recommended for broader access -VITE_GITHUB_TOKEN_TYPE= - -# ====================================== -# SERVICE INTEGRATIONS -# ====================================== - -# GitLab Personal Access Token -# Get your GitLab Personal Access Token here: -# https://gitlab.com/-/profile/personal_access_tokens -# Required scopes: api, read_repository, write_repository -VITE_GITLAB_ACCESS_TOKEN= - -# GitLab instance URL (e.g., https://gitlab.com or your self-hosted domain) -VITE_GITLAB_URL=https://gitlab.com - -# GitLab token type -VITE_GITLAB_TOKEN_TYPE=personal-access-token - -# Vercel Access Token -# Get your access token from: https://vercel.com/account/tokens -VITE_VERCEL_ACCESS_TOKEN= - -# Netlify Access Token -# Get your access token from: https://app.netlify.com/user/applications -VITE_NETLIFY_ACCESS_TOKEN= - -# Supabase Configuration -# Get your project details from: https://supabase.com/dashboard -VITE_SUPABASE_URL= -VITE_SUPABASE_ANON_KEY= -VITE_SUPABASE_ACCESS_TOKEN= - -# Example Context Values for qwen2.5-coder:32b -# -# DEFAULT_NUM_CTX=32768 # Consumes 36GB of VRAM -# DEFAULT_NUM_CTX=24576 # Consumes 32GB of VRAM -# DEFAULT_NUM_CTX=12288 # Consumes 26GB of VRAM -# DEFAULT_NUM_CTX=6144 # Consumes 24GB of VRAM -DEFAULT_NUM_CTX= - -FREE_OPENROUTER_API_KEY=your_openrouter_api_key_here diff --git a/.gitconfig b/.gitconfig new file mode 100644 index 00000000..1c1f57c7 --- /dev/null +++ b/.gitconfig @@ -0,0 +1,3 @@ +[user] + email = github@emergent.sh + name = emergent-agent-e1 diff --git a/.gitignore b/.gitignore index add2d896..40a09ac6 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,12 @@ favicon.png .collab-docs/ .runtime-workspaces/ .runtime/ +-e +# Environment and credential files +.env +.env.* +*.env +credentials.json +*.pem +*.key +.credentials diff --git a/CHANGELOG.md b/CHANGELOG.md index 2be15a8e..581cafad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,13 @@ ### Fixed +- Runtime command capture now enforces bounded output buffers for shell/start/build flows, preventing long install/build logs from growing unbounded in memory and reducing browser freezes during heavy runs. +- Terminal stream piping now safely swallows expected shutdown errors during process recycling, reducing unhandled stream rejection noise that previously appeared during rapid runtime resets. +- Shell write guards now also evaluate normalized/rewritten commands (including JSON command envelopes), closing a bypass path where blocked redirections could slip through after command rewrites. +- Shell write blocking now distinguishes harmless sink redirections (`/dev/null`) from real file writes, preserving portable runtime file checks without weakening mutation safeguards. +- Artifact file writes now reject any path resolving outside the active workspace root, hardening runtime file actions against path traversal attempts. +- WebContainer heartbeat checks now prevent overlapping probes, reducing race conditions that could trigger duplicate recovery attempts under heavy filesystem load. +- Chat bootstrap model-fetch and secure API key hydration now cancel cleanly on unmount, eliminating stale async state updates during rapid navigation and provider switches. - Hidden continuation prompts now wait until the active chat stream has actually settled before dispatching, which prevents overlapping chat requests from racing each other and reduces disconnect/reconnect loops during long runs. - Hosted FREE preview verification no longer auto-restores an older starter snapshot just because the browser briefly reported the fallback placeholder while the generated app files were already synced; starter-placeholder reporting is now ignored once the active workspace no longer contains the starter text. - Generated app entry-file writes now resolve onto the active starter source file when the model picks a sibling JS/TS extension (`App.js` vs `App.tsx`, `main.jsx` vs `main.tsx`, etc.), which stops hosted Vite projects from leaving the real app in an inactive file while preview keeps showing the fallback starter. diff --git a/FRAMEWORKS.md b/FRAMEWORKS.md index 29b60bae..964594bf 100644 --- a/FRAMEWORKS.md +++ b/FRAMEWORKS.md @@ -1,100 +1,77 @@ -# Multi-Framework Support for bolt.gives +# Desktop Framework Support for bolt.gives -This document describes how to build bolt.gives as a desktop application using different frameworks. +bolt.gives ships as a web app and as two desktop wrappers: -## Supported Frameworks +1. **Electron** (default) — broad-compatibility desktop app with auto-update and crash recovery. +2. **Tauri** (opt-in) — Rust-native lightweight desktop app with tight CSP and a signed updater. -1. **Electron** (default) - Classic desktop app framework -2. **Tauri** - Rust-based lightweight desktop app framework -3. **Neutralino.js** - Ultra-lightweight desktop app framework +> **Neutralino was removed.** The third wrapper caused build drift, duplicated window/menu/cookie code, and an unpatched update path. Electron remains the default; Tauri is the high-security / small-footprint option. -## Environment Variables +## Environment flags -You can control which framework(s) to build using environment variables: +| Flag | Default | Effect | +| ------------------ | ------- | ----------------------------------------------------- | +| `ENABLE_ELECTRON` | `true` | Set `false` to skip Electron in multi-target scripts. | +| `ENABLE_TAURI` | `false` | Set `true` to include Tauri in multi-target scripts. | -```bash -# Enable specific frameworks (default: electron is enabled by default) -ENABLE_ELECTRON=true pnpm electron:build:dist -ENABLE_TAURI=true pnpm tauri:build -ENABLE_NEUTRALINO=true pnpm neutralino:build +Accepted values: `1 / true / yes / on` and `0 / false / no / off` (case-insensitive). -# Disable a framework (e.g., disable Electron to only build Tauri) -ENABLE_ELECTRON=false ENABLE_TAURI=true pnpm tauri:build -``` +## Build commands -## Build Commands +### Electron -### Electron (Default) ```bash -# Development +# Dev pnpm electron:dev -# Build for distribution -pnpm electron:build:dist +# Platform-specific release bundles +pnpm electron:build:mac +pnpm electron:build:win +pnpm electron:build:linux -# Platform-specific builds -pnpm electron:build:mac # macOS DMG -pnpm electron:build:win # Windows NSIS -pnpm electron:build:linux # Linux AppImage/DEB +# All three (CI-only; produces .dmg / .exe / .AppImage / .deb / .rpm) +pnpm electron:build:dist ``` +Outputs land in `./dist/`. + ### Tauri + +Prerequisites: [Rust toolchain](https://tauri.app/start/prerequisites/) (`cargo`, `rustc`) and the Tauri CLI (`pnpm dlx @tauri-apps/cli@latest`). + ```bash -# Development (requires Rust and Tauri CLI) +# Dev pnpm tauri:dev -# Build for distribution +# Release bundle pnpm tauri:build ``` -### Neutralino.js -```bash -# Development -pnpm neutralino:dev +Outputs land in `./src-tauri/target/release/bundle/`. -# Build for distribution -pnpm neutralino:build -``` +## Security posture -## Framework-Specific Notes +| Surface | Electron | Tauri | +| -------------- | ----------------------------------------------------- | ------------------------------------------------------ | +| Context iso. | `contextIsolation: true`, `nodeIntegration: false` | `withGlobalTauri: false` | +| Sandbox | `sandbox: true` on BrowserWindow | Tauri default sandbox + capability scopes | +| CSP | Injected via `onHeadersReceived` (prod) | `tauri.conf.json` `security.csp` | +| Updater | `electron-updater` — signed via `electron-builder` config; fail-closed if no pubkey | `tauri-plugin-updater` — Ed25519 signature, fail-closed if `pubkey` empty | +| Crash recovery | `render-process-gone` + `child-process-gone` handlers respawn with backoff | Tauri native + `tauri-plugin-process` respawn | -### Tauri Requirements -- Rust toolchain -- Node.js and pnpm -- System dependencies (see [Tauri Prerequisites](https://tauri.app/v1/guides/getting-started/prerequisites)) +See the updater setup checklist in `docs/DESKTOP_UPDATER.md` (generated in Phase 5) for how to rotate keys. -Install Rust: -```bash -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -``` +## Release artifacts -### Neutralino.js Requirements -- Node.js and pnpm -- Neutralino CLI (optional, can use prebuilt binaries) +GitHub Actions build both frameworks on tag push (`v*`): -## Directory Structure +- `.github/workflows/electron.yml` → `dist/*.{dmg,exe,AppImage,deb,zip}` +- `.github/workflows/tauri.yml` → `src-tauri/target/release/bundle/**/*` -``` -/home/engine/project/ -├── src-tauri/ # Tauri configuration and Rust source -│ ├── src/ -│ ├── icons/ -│ └── capabilities/ -├── neutralino/ # Neutralino configuration -└── electron/ # Electron configuration (existing) -``` +Both workflows publish to the same GitHub Release. ## Troubleshooting -### Tauri Build Issues -- Ensure Rust is properly installed and up to date -- Check that all system dependencies are installed -- Run `rustup update` to update Rust toolchain - -### Neutralino Build Issues -- Ensure you have permission to create the output directory -- Check that the build/client directory exists - -### Electron Build Issues -- Clear node_modules and reinstall: `rm -rf node_modules && pnpm install` -- Check that all platform-specific dependencies are installed \ No newline at end of file +- **Tauri build fails with "failed to find tauri-cli"** — run `pnpm dlx @tauri-apps/cli@latest` or install globally: `cargo install tauri-cli --version '^2.0'`. +- **Electron updater silently does nothing** — that's intentional: builds without a valid `electron-update.yml` pubkey refuse to apply updates (fail-closed). See Phase 5 updater docs. +- **macOS "app is damaged" after download** — unsigned dev build; ship through GitHub Releases for `electron-builder` / Tauri to add notarization metadata. diff --git a/README.md b/README.md index d8661a4a..a68a0faa 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Recent hardening in this line includes safer hidden continuation dispatch: follow-up recovery prompts now wait until the active chat stream is genuinely idle before they are sent, which reduces mid-run disconnect/reconnect loops on longer builds. Another recent runtime fix now synthesizes preview handoff commands from the merged workspace state instead of the latest assistant delta only, which prevents broken follow-up runs from replaying dependency-only installs and stalling preview startup. +Runtime stabilization now also enforces bounded shell/build output capture and guarded heartbeat probing, reducing memory pressure and duplicate recovery races during long-running installs/builds. Release verification now also includes a browser-level post-deploy asset health check so stale manifests and missing client bundles fail the release instead of leaving users on a non-interactive shell. ### Launch blockers diff --git a/ROADMAP.md b/ROADMAP.md index e792151b..3ba0dd14 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -73,6 +73,7 @@ Release theme: make bolt.gives launch-safe for daily use by tightening prompt-to 3. Browser-weight reduction and server offload - [~] Continue cutting the remaining heavy browser chunks (`editor`, `pdf`, `git`, `terminal`). +- [~] Runtime shell/build output capture is now bounded and stream shutdown paths are guarded, reducing memory spikes and freeze risk during long installs/builds. - [ ] Push more preview/log reconciliation state entirely to the server. - [ ] Keep browser runtime surfaces thin enough that longer sessions no longer degrade visibly on lower-end machines. diff --git a/app/components/chat/APIKeyManager.tsx b/app/components/chat/APIKeyManager.tsx index ff689177..49a89dd6 100644 --- a/app/components/chat/APIKeyManager.tsx +++ b/app/components/chat/APIKeyManager.tsx @@ -1,8 +1,7 @@ import React, { useState, useEffect, useCallback } from 'react'; import { IconButton } from '~/components/ui/IconButton'; import type { ProviderInfo } from '~/types/model'; -import Cookies from 'js-cookie'; -import { getApiKeysFromCookies } from '~/lib/runtime/api-key-storage'; +import { getApiKeysFromCookies, setApiKeysCookie } from '~/lib/runtime/api-key-storage'; interface APIKeyManagerProps { provider: ProviderInfo; @@ -66,7 +65,7 @@ export const APIKeyManager: React.FC = ({ provider, apiKey, // Save to cookies const currentKeys = getApiKeysFromCookies(); const newKeys = { ...currentKeys, [provider.name]: normalizedKey }; - Cookies.set('apiKeys', JSON.stringify(newKeys), { expires: 365 }); + setApiKeysCookie(newKeys, 365); setIsEditing(false); }; diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index 47ad1f5a..80dd71bb 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -4,9 +4,13 @@ import { ClientOnly } from 'remix-utils/client-only'; import { Menu } from '~/components/sidebar/Menu.client'; import { classNames } from '~/utils/classNames'; import { PROVIDER_LIST } from '~/utils/constants'; -import { getApiKeysFromCookies } from '~/lib/runtime/api-key-storage'; +import { + getApiKeysFromCookies, + loadApiKeysFromSecureStorage, + removeApiKeysCookie, + setApiKeysCookie, +} from '~/lib/runtime/api-key-storage'; import { ChatBox } from './ChatBox'; -import Cookies from 'js-cookie'; import * as Tooltip from '@radix-ui/react-tooltip'; import styles from './BaseChat.module.scss'; import { ImportButtons } from '~/components/chat/chatExportAndImport/ImportButtons'; @@ -500,38 +504,68 @@ export const BaseChat = React.forwardRef( }, []); useEffect(() => { - if (typeof window !== 'undefined') { - let parsedApiKeys: Record | undefined = {}; - - try { - parsedApiKeys = getApiKeysFromCookies(); - setApiKeys(parsedApiKeys); - } catch (error) { - console.error('Error loading API keys from cookies:', error); - Cookies.remove('apiKeys'); - } + if (typeof window === 'undefined') { + return; + } - setIsModelLoading('all'); - fetch('/api/models') - .then((response) => response.json()) - .then((data) => { - const typedData = data as { modelList: ModelInfo[] }; - setModelList(typedData.modelList); - }) - .catch((error) => { - console.error('Error fetching model list:', error); - }) - .finally(() => { - setIsModelLoading(undefined); + let disposed = false; + const modelsRequestController = new AbortController(); + let parsedApiKeys: Record | undefined = {}; + + try { + parsedApiKeys = getApiKeysFromCookies(); + setApiKeys(parsedApiKeys); + + if (Object.keys(parsedApiKeys).length === 0) { + void loadApiKeysFromSecureStorage().then((secureApiKeys) => { + if (disposed || Object.keys(secureApiKeys).length === 0) { + return; + } + + setApiKeys(secureApiKeys); + setApiKeysCookie(secureApiKeys); }); + } + } catch (error) { + console.error('Error loading API keys from cookies:', error); + removeApiKeysCookie(); } + + setIsModelLoading('all'); + fetch('/api/models', { signal: modelsRequestController.signal }) + .then((response) => response.json()) + .then((data) => { + if (disposed) { + return; + } + + const typedData = data as { modelList: ModelInfo[] }; + setModelList(typedData.modelList); + }) + .catch((error) => { + if (error?.name === 'AbortError') { + return; + } + + console.error('Error fetching model list:', error); + }) + .finally(() => { + if (!disposed) { + setIsModelLoading(undefined); + } + }); + + return () => { + disposed = true; + modelsRequestController.abort(); + }; }, [providerListSignature]); const onApiKeysChange = async (providerName: string, apiKey: string) => { const normalizedApiKey = apiKey.trim(); const newApiKeys = { ...apiKeys, [providerName]: normalizedApiKey }; setApiKeys(newApiKeys); - Cookies.set('apiKeys', JSON.stringify(newApiKeys), { expires: 365 }); + setApiKeysCookie(newApiKeys, 365); setIsModelLoading(providerName); diff --git a/app/components/chat/Chat.client.tsx b/app/components/chat/Chat.client.tsx index a971f3fd..c7307bed 100644 --- a/app/components/chat/Chat.client.tsx +++ b/app/components/chat/Chat.client.tsx @@ -39,7 +39,6 @@ import { LOCAL_PROVIDERS } from '~/lib/stores/settings'; import { LAST_CONFIGURED_PROVIDER_COOKIE_KEY, getRememberedProviderModel, - parseApiKeysCookie, pickPreferredProviderName, recordProviderHistory, readInstanceSelection, @@ -66,6 +65,7 @@ import type { import { shouldUnlockPromptAfterPreviewReady } from './execution-status'; import { hasFallbackStarterPlaceholder, STARTER_PLACEHOLDER_TEXT } from '~/lib/runtime/starter-placeholder'; import { getHiddenContinuationDelay, shouldDispatchHiddenContinuation } from '~/lib/runtime/continuation-dispatch'; +import { getApiKeysFromCookies, setApiKeysCookie } from '~/lib/runtime/api-key-storage'; const logger = createScopedLogger('Chat'); const ARCHITECT_NAME = 'Architect'; @@ -161,7 +161,7 @@ function loadStoredProjectMemory(): StoredProjectMemory { } function getApiKeysFromCookiesSafe(): Record { - return parseApiKeysCookie(Cookies.get('apiKeys')); + return getApiKeysFromCookies(); } function getProviderSettingsFromCookiesSafe(): Record { @@ -2387,7 +2387,7 @@ Requirements: if (temResp) { const { assistantMessage, usingLocalFallback } = temResp; - const starterActionCount = (assistantMessage.match(/ { setApiKeys(updatedApiKeys); - Cookies.set('apiKeys', JSON.stringify(updatedApiKeys), { expires: CHAT_SELECTION_COOKIE_EXPIRY_DAYS }); + setApiKeysCookie(updatedApiKeys, CHAT_SELECTION_COOKIE_EXPIRY_DAYS); const normalizedKey = apiKey.trim(); diff --git a/app/components/chat/ChatAlert.tsx b/app/components/chat/ChatAlert.tsx index 773c6809..cdb7c0ee 100644 --- a/app/components/chat/ChatAlert.tsx +++ b/app/components/chat/ChatAlert.tsx @@ -24,8 +24,8 @@ export default function ChatAlert({ alert, clearAlert, postMessage, autoFixState ? 'Architect has queued an automatic preview repair and will run it as soon as the current step finishes.' : 'Architect has queued an automatic terminal repair and will run it as soon as the current step finishes.' : isPreview - ? 'We encountered an error while running the preview. Would you like Bolt to analyze and help resolve this issue?' - : 'We encountered an error while running terminal commands. Would you like Bolt to analyze and help resolve this issue?'; + ? 'We encountered an error while running the preview. Would you like Cody agent to analyze and help resolve this issue?' + : 'We encountered an error while running terminal commands. Would you like Cody agent to analyze and help resolve this issue?'; return ( @@ -96,7 +96,7 @@ export default function ChatAlert({ alert, clearAlert, postMessage, autoFixState )} >
- {autoFixState === 'running' ? 'Auto-fixing' : autoFixState === 'queued' ? 'Queued' : 'Ask Bolt'} + {autoFixState === 'running' ? 'Auto-fixing' : autoFixState === 'queued' ? 'Queued' : 'Ask Cody agent'}