From ce16a87cee8d54f21f875639b9202f2ecffddf33 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Mon, 11 May 2026 16:08:46 +0800 Subject: [PATCH 1/7] chore: prepare v2.0.0 release --- README.md | 30 ++++------ .../_components/page-header-with-filter.tsx | 2 +- .../_components/project-actions-menu.tsx | 2 +- app/(dashboard)/projects/(list)/page.tsx | 2 +- .../_components/github-status-card.tsx | 2 +- .../settings/_components/settings-sidebar.tsx | 1 - app/(landing)/_components/hero-section.tsx | 4 +- app/(landing)/_components/landing-client.tsx | 2 +- app/(landing)/_components/terminal-demo.tsx | 6 +- app/layout.tsx | 4 +- components/sidebar.tsx | 14 +---- components/terminal/toolbar/toolbar.tsx | 3 - docs/releases/v2.0.0.md | 57 +++++++++++++++++++ docs/wiki/features/user-settings.md | 3 +- docs/wiki/routing.md | 10 +--- docs/wiki/todo.md | 8 +-- package.json | 2 +- 17 files changed, 90 insertions(+), 62 deletions(-) create mode 100644 docs/releases/v2.0.0.md diff --git a/README.md b/README.md index e2b89fd..303bb45 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Fulling - AI-Powered Full-Stack Development Platform
- Version 2.0.0-dev + Version 2.0.0 Next.js TypeScript PostgreSQL @@ -9,29 +9,22 @@ Claude Code
-> [!CAUTION] -> **v2 development in progress** -> -> We're rearchitecting Fulling as an Agentic app. Breaking changes expected. -> -> For stable, use [`v1.0.0`](https://github.com/FullAgent/fulling/tree/v1.0.0). - ## What is Fulling? **Fulling lets you focus on coding. AI handles everything else.** -Import your project from GitHub or start fresh. Claude Code is your AI pair programmer—it writes code, runs tests, manages databases, and deploys to production. All in a browser-based development environment. +Import your project from GitHub or start fresh. Claude Code is your AI pair programmer in a browser-based development environment with a terminal, file manager, live URLs, and optional PostgreSQL. -**Configuration-driven development.** Need Stripe? OAuth? Just enter your API keys in project settings. Services become instantly available—no SDK setup, no environment variables, no integration code. Claude Code reads your config and implements the features for you. +**Configuration-aware development.** Add project environment variables, secrets, auth keys, or payment keys in the UI. Claude Code can read that project context and use it while implementing features. ## Overview -Fulling provides a sandboxed environment with Claude Code and PostgreSQL — everything you need to vibe code full-stack apps. +Fulling v2 provides a sandboxed development environment with Claude Code, GitHub import, optional PostgreSQL, and asynchronous background reconciliation for project resources. -Fulling automatically sets up everything you need, ready in a minute: +Fulling automatically sets up the core workspace, ready in a minute: - AI pair programmer (Claude Code) - Full-stack development environment -- Dedicated database (PostgreSQL) +- Optional dedicated database (PostgreSQL) - Web terminal & file manager - Live HTTPS domains @@ -43,7 +36,7 @@ Fulling automatically sets up everything you need, ready in a minute: - **AI Pair Programmer** - Claude Code is pre-installed and ready. Describe what you want, it writes the code. -- **Zero Setup** - Full-stack environment with database, terminal, and file manager. All pre-configured, ready in seconds. +- **Zero Setup Workspace** - Full-stack environment with terminal, file manager, and live URLs. All pre-configured, ready in seconds. - **Web Terminal** - Full Linux terminal in your browser. Run commands, install packages, debug—everything you'd do locally. @@ -51,11 +44,11 @@ Fulling automatically sets up everything you need, ready in a minute: - **Live Domains** - Your app gets HTTPS URLs instantly. No port forwarding, no ngrok. Just run and share. -- **Configuration-Driven** - Add Stripe, OAuth, or any service by entering API keys in settings. Claude Code reads your config and implements the integration for you. +- **Configuration-Aware** - Add environment variables, secrets, auth keys, and payment keys in settings. Claude Code can read your config and implement against it. - **GitHub Integration** - Import repos, push changes, version control. Works like you'd expect. -- **One-Click Deploy** - From sandbox to production in one click. No YAML, no CI/CD pipelines. +- **Project Task Automation** - GitHub import and skill installation run through persisted background tasks after the sandbox is ready. ## Built With @@ -72,7 +65,7 @@ For technical details, see [Architecture Documentation](./docs/architecture.md). - Node.js 22.12.0 or higher - PostgreSQL database - Kubernetes cluster with KubeBlocks installed -- GitHub OAuth application credentials +- GitHub App and OAuth application credentials ### Environment Setup @@ -178,6 +171,7 @@ fulling/ - [Development Guide](./docs/development.md) - Local development - [Operations Manual](./docs/operations.md) - Deployment, monitoring - [Troubleshooting](./docs/troubleshooting.md) - Common issues +- [v2.0.0 Release Notes](./docs/releases/v2.0.0.md) - Release scope and migration notes ## Security @@ -203,7 +197,7 @@ See [Contributing Guidelines](CONTRIBUTING.md) for details. MIT License - see [LICENSE](LICENSE). -See [CHANGELOG.md](CHANGELOG.md) for release history. +See [v2.0.0 Release Notes](./docs/releases/v2.0.0.md) for the current release scope. ## Acknowledgments diff --git a/app/(dashboard)/projects/(list)/_components/page-header-with-filter.tsx b/app/(dashboard)/projects/(list)/_components/page-header-with-filter.tsx index 679aad9..37c781f 100644 --- a/app/(dashboard)/projects/(list)/_components/page-header-with-filter.tsx +++ b/app/(dashboard)/projects/(list)/_components/page-header-with-filter.tsx @@ -27,7 +27,7 @@ export function PageHeaderWithFilter({ activeFilter, onFilterChange }: PageHeade My Projects

- Manage and monitor your full stack apps and deployments. + Manage and monitor your full stack project workspaces.

{ - router.push(`/projects/${projectId}/settings`); + router.push(`/projects/${projectId}/environment`); }; return ( diff --git a/app/(dashboard)/projects/(list)/page.tsx b/app/(dashboard)/projects/(list)/page.tsx index 430cbb9..7764b48 100644 --- a/app/(dashboard)/projects/(list)/page.tsx +++ b/app/(dashboard)/projects/(list)/page.tsx @@ -7,7 +7,7 @@ import { HomePageContent } from './_components/home-page-content' export const metadata = { title: 'My Projects | Fulling', - description: 'Manage and monitor your full stack apps and deployments.', + description: 'Manage and monitor your full stack project workspaces.', } export default async function HomePage() { diff --git a/app/(dashboard)/settings/_components/github-status-card.tsx b/app/(dashboard)/settings/_components/github-status-card.tsx index 7c1b5f5..b8207f8 100644 --- a/app/(dashboard)/settings/_components/github-status-card.tsx +++ b/app/(dashboard)/settings/_components/github-status-card.tsx @@ -121,7 +121,7 @@ export function GitHubStatusCard() { What does the GitHub Integration do?

- Connecting to GitHub allows you to sync your project to Fulling, and automatically deploys your production-ready project to Sealos. + Connecting to GitHub allows you to import repositories and keep project source metadata available in Fulling.

{installation ? ( diff --git a/app/(dashboard)/settings/_components/settings-sidebar.tsx b/app/(dashboard)/settings/_components/settings-sidebar.tsx index bb979b2..7015687 100644 --- a/app/(dashboard)/settings/_components/settings-sidebar.tsx +++ b/app/(dashboard)/settings/_components/settings-sidebar.tsx @@ -8,7 +8,6 @@ import { cn } from '@/lib/utils' const menuItems = [ { label: 'Integrations', href: '/settings/integrations' }, - { label: 'Account', href: '/settings/account' }, ] export function SettingsSidebar() { diff --git a/app/(landing)/_components/hero-section.tsx b/app/(landing)/_components/hero-section.tsx index 8d65239..d736bad 100644 --- a/app/(landing)/_components/hero-section.tsx +++ b/app/(landing)/_components/hero-section.tsx @@ -45,7 +45,7 @@ export function HeroSection({ {/* Subtitle */}

Import an existing repo or start from scratch. Agents set up the workspace, runtime, - database, and deployment in the background. + optional database, and project automation in the background.

{/* Error message */} @@ -109,7 +109,7 @@ const FEATURES_JSX = (
- Agents handle deploy, runtime, and infra automatically + Agents handle runtime and project setup automatically
); diff --git a/app/(landing)/_components/landing-client.tsx b/app/(landing)/_components/landing-client.tsx index 3480da8..50f522d 100644 --- a/app/(landing)/_components/landing-client.tsx +++ b/app/(landing)/_components/landing-client.tsx @@ -21,7 +21,7 @@ interface LandingClientProps { * Handles all interactive logic (auth, navigation) while receiving * server-fetched data (starCount) as props. * - * Authentication Flow (v2.0.0-alpha-3): + * Authentication Flow (v2.0.0): * - Sealos environment: Auto-trigger auth on page load if unauthenticated * - Non-Sealos + Authenticated: Show "Go to Projects" button * - Non-Sealos + Unauthenticated: Show "Start Building Now" → /login diff --git a/app/(landing)/_components/terminal-demo.tsx b/app/(landing)/_components/terminal-demo.tsx index 56d463e..fb1a4db 100644 --- a/app/(landing)/_components/terminal-demo.tsx +++ b/app/(landing)/_components/terminal-demo.tsx @@ -47,7 +47,7 @@ export function TerminalDemo() { Provisioning PostgreSQL cluster...
- Wiring runtime and deployment... + Wiring runtime and project config...
@@ -61,7 +61,7 @@ export function TerminalDemo() { - +
@@ -73,7 +73,7 @@ export function TerminalDemo() {
- +
{/* Success Card */} diff --git a/app/layout.tsx b/app/layout.tsx index a7243f1..cf94dd4 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -20,8 +20,8 @@ const notoSans = Noto_Sans({ export const metadata: Metadata = { title: 'Fulling - AI-Powered Full-Stack Development', - description: 'Create, develop, and deploy production-ready web applications using natural language. Powered by ' + - 'Claude Code in isolated sandbox environments.', + description: + 'Create or import full-stack projects in isolated browser sandboxes powered by Claude Code.', }; export default function RootLayout({ diff --git a/components/sidebar.tsx b/components/sidebar.tsx index e99d9bb..18691ae 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -2,10 +2,7 @@ import { FaGithub } from 'react-icons/fa6' import { - MdDashboardCustomize, - MdExtension, MdGridView, - MdHub, MdLightbulbOutline, MdPsychology, MdSettings, @@ -15,17 +12,12 @@ import Link from 'next/link' import { usePathname } from 'next/navigation' import { Button } from '@/components/ui/button' -import { Separator } from '@/components/ui/separator' import packageInfo from '../package.json' const menuItems = [ { icon: MdGridView, label: 'Projects', href: '/projects', active: false }, { icon: MdPsychology, label: 'Skills', href: '/skills', active: false }, - { icon: MdHub, label: 'MCP', href: '/mcp', active: false }, - { divider: true }, - { icon: MdDashboardCustomize, label: 'Templates', href: '/templates', active: false }, - { icon: MdExtension, label: 'Integrations', href: '/integrations', active: false }, { icon: MdSettings, label: 'Settings', href: '/settings', active: false }, ] @@ -68,11 +60,7 @@ function NavMenu() { return ( + +
+
+
Persistent AI Workspace
+

+ Turn every working relationship into a + durable environment. +

+

+ Fulling organizes skills, files, memory, scripts, and runtime into one calm operating surface. + The result feels less like a chat window and more like a place where work can keep living. +

+
+ + +
+
+
+
+
12m 47s
+
from blank profile to usable workspace
+
+
+
+
+
34
+
workspace files indexed for context
+
+
+
+
+
runtime: warm
+
scripts, terminal, and memory ready for the next session
+
+
+
+
+ +
+
+
+
+
+ + + +
+ workspace/runtime + active +
+
+ $ fulling workspace init iris + skills mounted: brief-writer, research-scan, deploy-check + memory attached: project preferences, tone notes, file map + runtime policy: ask before destructive shell operations + $ run prepare-session + scripts verified in isolated shell +
+ Workspace ready. The environment can be resumed, edited, shared, or archived without rebuilding context. + +
+
+
+
+
+ memory.lock + updated 18 minutes ago +
+
+ skill graph + 7 capabilities available +
+
+
+
+ +
+
+
+
Control Surface
+

The application view keeps the system quiet, inspectable, and ready for repeated use.

+
+

+ This is the same visual language applied to an app surface: sidebar navigation, segmented status, + asymmetric panels, live memory, and a small script console. +

+
+ +
+
+ + +
+
+
+
Session 04
+

Workspace readiness

+
+
+ + synced 2m ago +
+
+ +
+
+
+
+ Memory and working files + 34 files +
+
+
+
SK
+
+ Research briefing skill +

Reads notes, drafts structured summaries, and keeps citations attached.

+
+ skill +
+
+
MM
+
+ Personal working memory +

Stores style preferences, project facts, and recurring review criteria.

+
+ memory +
+
+
FS
+
+ Workspace file map +

Tracks active drafts, inputs, scripts, and generated artifacts.

+
+ files +
+
+
+
+ +
+
+
+ Script console + runtime +
+
+ $ ./scripts/check-context + memory graph consistent + file permissions verified + pending approval: publish report + $ ./scripts/start-session +
+
+
+
+ +
+
+
+
+ Context load + active +
+
47.2%
+

Current session uses prior memory without saturating the window.

+
+
+ +
+
+
+ Recent reconciliation + queue +
+
+
10:34skill mounted
+
10:36files indexed
+
10:41runtime warmed
+
10:48memory saved
+
+
+
+
+
+
+
+
+ + + + + diff --git a/docs/architecture-evolution.md b/docs/architecture-evolution.md deleted file mode 100644 index 065d572..0000000 --- a/docs/architecture-evolution.md +++ /dev/null @@ -1,421 +0,0 @@ -# Architecture Evolution - -This document describes the intended next stage of Fulling's codebase evolution. - -It is not a description of the current repository layout. It is the target direction for making the code structure match the system architecture more directly. - -## Why Evolve - -Fulling is no longer just a conventional web application. - -It is a platform with: - -- a database-backed control plane -- resource reconciliation for Kubernetes infrastructure -- project-level asynchronous task execution inside sandboxes -- multiple external integrations such as GitHub, ttyd, Kubernetes, and future deploy providers - -As the system grows, a framework-shaped code layout stops matching the real mental model of the platform. - -The goal of the next phase is to make the codebase express the system in the same terms we use to reason about it: - -```text -Intent -> State -> Reconcile -> Effect -``` - -## Target Mental Model - -The ideal architecture should be understandable as five layers: - -1. Interaction Layer -2. Control State Layer -3. Orchestration Layer -4. Execution Layer -5. Integration Layer - -A reader should be able to answer these questions quickly: - -- Where does user intent enter the system? -- Where is that intent persisted as state? -- Which background mechanism advances that state? -- Which code actually performs the external effect? -- Which files encapsulate external system protocols? - -## Target Architecture - -### 1. Interaction Layer - -Purpose: - -- receive requests from users or clients -- parse input -- call control-plane commands or queries -- return responses - -Examples: - -- Next.js route handlers -- Server Actions -- UI entry components - -This layer should not contain infrastructure lifecycle logic or long-running orchestration logic. - -### 2. Control State Layer - -Purpose: - -- convert user intent into persisted state transitions -- define command and query use cases -- decide which records must be created or updated - -Examples: - -- create project -- import project from GitHub -- create database -- dispatch install-skill tasks -- dispatch deploy task - -This layer is where the system decides what should happen, not where it actually performs it. - -### 3. Orchestration Layer - -Purpose: - -- scan persisted state -- evaluate state machine transitions -- determine whether prerequisites are met -- claim work and advance it safely - -Examples: - -- sandbox reconcile -- database reconcile -- project task reconcile - -This layer should own "when work is ready to run" and "what transition comes next." - -### 4. Execution Layer - -Purpose: - -- perform the actual work after orchestration decides it should happen - -Examples: - -- create sandbox in Kubernetes -- stop database cluster -- clone repository inside sandbox -- install skill in sandbox -- deploy project - -This layer should be effectful, explicit, and easy to test in isolation. - -### 5. Integration Layer - -Purpose: - -- isolate external system protocol details from internal orchestration and domain logic - -Examples: - -- GitHub App token exchange and installation APIs -- ttyd command execution -- Kubernetes service and managers -- deploy provider clients -- AI proxy integration - -This layer should be the only place that knows provider-specific protocol details. - -## Ideal Repository Shape - -The repository should gradually move toward a platform-shaped layout, with -`lib/platform/` acting as the main container for control-plane code: - -```text -app/ - Framework entrypoints only - -lib/ - domain/ - platform/ - control/ - commands/ - queries/ - persistence/ - orchestrators/ - resources/ - tasks/ - executors/ - k8s/ - sandbox/ - deploy/ - integrations/ - github/ - ttyd/ - k8s/ - aiproxy/ - policies/ - shared/ -``` - -This is not a requirement to rename everything immediately. It is the target shape that best matches the architecture. - -## What Each Target Area Means - -### `app/` - -Should contain: - -- routes -- pages -- route-local UI composition -- request parsing -- response shaping - -Should not be the place where major platform logic lives. - -### `lib/domain/` - -Should contain: - -- state semantics -- status aggregation rules -- lifecycle definitions -- prerequisite evaluation logic -- domain types and invariants - -Typical examples: - -- project status aggregation -- task prerequisite rules -- lifecycle transition rules - -### `lib/platform/control/` - -Should contain: - -- application commands -- application queries -- user-intent entrypoints that are independent from framework details - -Typical examples: - -- `create-project` -- `import-project-from-github` -- `dispatch-install-skill-for-user-projects` -- `get-project-list` - -This is the layer that turns interaction into durable state changes. - -### `lib/platform/persistence/` - -Should contain: - -- database access -- row claiming -- lock management -- state transition persistence - -This corresponds closely to what `lib/repo/` does today, but relocated under -`platform/` so the control plane and its persistence boundaries live in one -coherent top-level area. - -### `lib/platform/orchestrators/` - -Should contain: - -- reconcile loops -- transition selection -- scheduling logic -- wake-up logic for resource or task processing - -This layer is currently spread across `jobs/` and `events/`. - -Long term, the code should make it easy to inspect one workflow in one place, instead of hopping across multiple implementation-mechanism directories. - -### `lib/platform/executors/` - -Should contain: - -- effectful operations triggered by orchestrators - -Examples: - -- Kubernetes resource actions -- sandbox command execution -- deployment actions - -The key separation is: - -- orchestrators decide whether to run -- executors perform the work - -### `lib/platform/integrations/` - -Should contain: - -- provider-specific APIs -- transport/protocol code -- external authentication/token exchange - -This is especially important because `services/` and `util/` currently mix together: - -- true business services -- external adapters -- generic helpers - -That mixing increases cognitive load. - -## Structural Rules For The Next Phase - -### Rule 1: Keep resource plane and task plane distinct - -Resource lifecycle and project task execution are related but not the same. - -- `Sandbox` and `Database` belong to the resource plane -- `ProjectTask` belongs to the task plane - -They should not collapse into a single generic abstraction too early. - -### Rule 2: Prefer architecture-shaped names over framework-shaped names - -Names should describe system role, not just invocation style. - -Examples: - -- better: `control`, `orchestrators`, `integrations`, `executors` -- weaker: `services`, `util`, `helpers` - -### Rule 3: Polling remains the correctness mechanism - -Wake-up triggers are useful, but background reconcile loops remain the source of truth. - -The architecture should continue to optimize for recoverability, not only low latency. - -### Rule 4: State machines should remain explicit - -If work needs to resume later, it should be represented in persisted state. - -Avoid moving important lifecycle meaning into hidden in-memory logic. - -### Rule 5: Reorganization should follow system boundaries, not file count - -The purpose of the reorganization is not aesthetic. - -It is to make the codebase reflect the platform's actual conceptual boundaries: - -- intent handling -- persisted state -- orchestration -- execution -- external integration - -### Rule 6: Prefer one primary platform action per file - -Within `lib/platform/`, prefer files that exist to express one primary platform action. - -Examples: - -- `create-project-from-github.ts` -- `create-clone-repository-task.ts` -- `find-installation-repository.ts` -- `get-user-default-namespace.ts` - -This improves scanability. A reader should be able to infer the main responsibility -of a file from its name before opening it. - -### Rule 7: Use noun directories and verb file names - -Within `lib/platform/`: - -- directories should describe the boundary or subsystem -- files should describe the primary action they perform - -Examples: - -- good directory names: `control`, `persistence`, `integrations`, `project-task` -- good file names: `create-project-with-sandbox`, `create-clone-repository-task`, `find-github-installation-by-id` -- weaker file names: `shared`, `github`, `repository-access`, `user-namespace` - -### Rule 8: Primary platform functions must carry boundary comments - -If a file in `lib/platform/` exists to perform one primary action, its main exported -function should have a short comment that explains: - -- purpose -- expected inputs or preconditions -- expected outputs or guarantees -- what is explicitly out of scope - -The goal is not comment volume. The goal is to make the file's architectural role -obvious without reading the full implementation. - -## Suggested Migration Strategy - -The next phase should be gradual. - -### Phase 1: Clarify naming and ownership - -Goals: - -- continue building `ProjectTask` as the task-plane abstraction -- reduce ambiguous use of `services/` and `util/` -- document system ownership by layer - -Low-risk changes: - -- move external adapters toward `integrations/` -- move business rules toward `domain/` or `policies/` -- introduce command/query naming for control-plane operations - -### Phase 2: Reshape orchestration boundaries - -Goals: - -- make workflows easier to inspect end-to-end -- reduce mental jumps between job and listener implementations - -Likely work: - -- make resource orchestration more visibly grouped -- make task orchestration more visibly grouped -- separate transition logic from executor logic more explicitly - -### Phase 3: Unify platform patterns where appropriate - -Goals: - -- reduce duplicated scheduling and transition mechanics -- strengthen task dependency and execution models - -Examples: - -- generalized task prerequisite evaluation -- shared orchestration helpers for claiming and transition persistence -- richer task types such as deploy, install skill, uninstall skill - -This phase should happen only after the architecture is clearly expressed in the codebase. - -## What "Success" Looks Like - -The reorganization is successful when the following are true: - -- a new engineer can trace a major flow without guessing layer ownership -- resource lifecycles are easy to inspect in one conceptual area -- task execution flows are easy to inspect in one conceptual area -- external provider code is clearly separated from domain and orchestration code -- adding a new task type does not require inventing a new architectural path - -## Current Direction - -The current `ProjectTask` introduction is the first concrete move in this direction. - -It shifts GitHub import from a project-specific status track into a reusable task-plane abstraction. - -That same direction should guide the next structural changes: - -- task types should grow through the task plane -- external adapters should become more explicit -- orchestration should become easier to inspect -- the repository layout should increasingly mirror the architecture itself diff --git a/docs/architecture.md b/docs/architecture.md deleted file mode 100644 index 09458df..0000000 --- a/docs/architecture.md +++ /dev/null @@ -1,453 +0,0 @@ -# Architecture - -This document describes the current runtime architecture of Fulling and the roles of the major subsystems in the repository. - -## Overview - -Fulling is a database-driven control plane for project sandboxes. - -The system is organized around three ideas: - -1. User-facing code records intent in PostgreSQL. -2. Background reconcile jobs read persisted state and advance it asynchronously. -3. External effects happen through two execution layers: - - Kubernetes resource control for sandboxes and databases - - Project task execution inside ready sandboxes - -This is not a request/response system that blocks on infrastructure. It is an asynchronous state-convergence system. - -## High-Level Model - -There are three major domains: - -- Control plane - - Next.js pages, Server Actions, and API routes - - Authentication and authorization - - Prisma models as the source of truth -- Resource plane - - `Sandbox` and `Database` lifecycle management - - Kubernetes operations through user-scoped services -- Task plane - - `ProjectTask` records for project-level asynchronous work - - Current use case: clone a GitHub repository into a sandbox - - Future use cases: install skill, uninstall skill, deploy project - -At a high level: - -```text -User action --> App code validates and writes desired state to DB --> Reconcile jobs scan DB for records in transitional states --> Event listeners / task executors perform external work --> Resource/task state is updated in DB --> UI polls and reflects the latest state -``` - -## Repository Structure - -```text -app/ - UI routes, API routes, and route-local components - -components/ - Shared UI components - -lib/actions/ - Server Actions called from client components - -lib/data/ - Server-side data access for React Server Components - -lib/repo/ - Persistence helpers, locking, and state transitions - -lib/jobs/ - Background reconcile loops - -lib/events/ - Resource event buses and listeners - -lib/k8s/ - User-scoped Kubernetes service and managers - -lib/services/ - Cross-cutting services and task dispatch helpers - -lib/util/ - Aggregation, ttyd execution, formatting, and helpers - -prisma/ - Prisma schema and migrations -``` - -## Core Runtime Layers - -### 1. User Interaction Layer - -Primary locations: - -- `app/` -- `lib/actions/` -- `lib/data/` - -Responsibilities: - -- Authenticate the user -- Validate inputs -- Decide whether an operation is allowed -- Write the resulting state to the database -- Return immediately without waiting on Kubernetes or long-running sandbox work - -Examples: - -- Create new project - - create `Project` - - create `Sandbox` - - create built-in environment variables -- Import from GitHub - - create `Project` - - create `Sandbox` - - create a `ProjectTask` of type `CLONE_REPOSITORY` -- Add database - - create `Database` with status `CREATING` -- Update environment variables - - persist environment changes - - mark running sandboxes as `UPDATING` - -### 2. State Persistence Layer - -Primary locations: - -- `prisma/schema.prisma` -- `lib/repo/` - -The database is the durable control plane. - -The key models are: - -- `Project` - - project metadata - - aggregated project status - - optional GitHub repository metadata -- `Sandbox` - - sandbox lifecycle state - - URLs and runtime resource configuration -- `Database` - - PostgreSQL lifecycle state - - connection credentials once ready -- `Environment` - - project-scoped environment variables -- `ProjectTask` - - project-level asynchronous work - - payload, result, retries, and locks -- `GitHubAppInstallation` - - GitHub App installation ownership and permissions - -The repository layer is where row locking and state transitions are centralized. - -## Resource Plane - -The resource plane manages infrastructure resources that exist independently in Kubernetes. - -### Sandbox Lifecycle - -Primary files: - -- `lib/jobs/sandbox/sandboxReconcile.ts` -- `lib/events/sandbox/sandboxListener.ts` -- `lib/repo/sandbox.ts` -- `lib/k8s/sandbox-manager.ts` - -States: - -- `CREATING` -- `STARTING` -- `RUNNING` -- `UPDATING` -- `STOPPING` -- `STOPPED` -- `TERMINATING` -- `TERMINATED` -- `ERROR` - -Flow: - -```text -Sandbox.status = CREATING --> sandbox reconcile job locks the row --> emits CreateSandbox --> listener creates K8s resources and writes ingress URLs --> Sandbox.status = STARTING --> later reconcile checks K8s status --> Sandbox.status = RUNNING -``` - -Environment updates reuse the same mechanism: - -```text -Environment changed --> running sandboxes marked UPDATING --> update sandbox env vars in Kubernetes --> pod restarts if needed --> sandbox returns to STARTING or RUNNING -``` - -### Database Lifecycle - -Primary files: - -- `lib/jobs/database/databaseReconcile.ts` -- `lib/events/database/databaseListener.ts` -- `lib/repo/database.ts` -- `lib/k8s/database-manager.ts` - -States: - -- `CREATING` -- `STARTING` -- `RUNNING` -- `STOPPING` -- `STOPPED` -- `TERMINATING` -- `TERMINATED` -- `ERROR` - -Flow: - -```text -Database.status = CREATING --> database reconcile job locks the row --> emits CreateDatabase --> listener creates KubeBlocks cluster --> Database.status = STARTING --> later reconcile checks cluster status --> credentials are fetched --> Database.status = RUNNING -``` - -### Project Status Aggregation - -Primary file: - -- `lib/util/projectStatus.ts` - -`Project.status` is derived from child resource states. It is not the main driver of work. - -Priority order: - -1. `ERROR` -2. `CREATING` -3. `UPDATING` -4. all resources equal the same stable state -5. consistent mixed transitions: - - `{RUNNING, STARTING}` -> `STARTING` - - `{STOPPED, STOPPING}` -> `STOPPING` - - `{TERMINATED, TERMINATING}` -> `TERMINATING` -6. otherwise `PARTIAL` - -When a project has no remaining resources, the project and its environments are deleted. - -## Task Plane - -The task plane manages project-level work that happens after a sandbox is ready. - -Primary files: - -- `lib/jobs/project-task/projectTaskReconcile.ts` -- `lib/jobs/project-task/executors/` -- `lib/repo/project-task.ts` -- `lib/services/project-task-dispatcher.ts` - -### Current Task Types - -- `CLONE_REPOSITORY` -- `INSTALL_SKILL` -- `UNINSTALL_SKILL` -- `DEPLOY_PROJECT` - -Only `CLONE_REPOSITORY` is implemented today. - -### Task States - -- `PENDING` -- `WAITING_FOR_PREREQUISITES` -- `RUNNING` -- `SUCCEEDED` -- `FAILED` -- `CANCELLED` - -### Task Flow - -```text -User imports a GitHub repository --> app creates Project + Sandbox --> app creates ProjectTask(type=CLONE_REPOSITORY, status=WAITING_FOR_PREREQUISITES) --> sandbox reaches RUNNING --> task reconcile sees prerequisites are now satisfied --> task executor runs git clone inside the sandbox through ttyd --> task becomes SUCCEEDED or FAILED -``` - -Task execution data lives in: - -- `payload` - - executor input, such as repo metadata or skill id -- `result` - - executor output, such as imported path -- `error` - - terminal error message for failed tasks - -## Polling and Triggering - -The system uses both polling and direct wake-up triggers. - -### Polling - -Background jobs continuously scan the database: - -- sandbox reconcile job -- database reconcile job -- project task reconcile job - -This is the correctness mechanism. - -### Direct wake-up triggers - -Some transitions accelerate work without replacing polling. - -Example: - -- when a sandbox becomes `RUNNING`, sandbox listeners call `triggerRunnableTasksForProject(projectId)` - -This reduces latency, but correctness still depends on periodic reconcile loops. - -## Locking Model - -The system uses database-based optimistic coordination, not an external queue. - -Patterns: - -- resource rows (`Sandbox`, `Database`) have `lockedUntil` -- task rows (`ProjectTask`) also have `lockedUntil` -- reconcile queries atomically select and lock eligible rows -- row-level transitions are updated in repo helpers - -This avoids duplicate processing across concurrent app instances. - -## Kubernetes Integration - -Primary file: - -- `lib/k8s/k8s-service-helper.ts` - -Rule: - -- always obtain Kubernetes access through `getK8sServiceForUser(userId)` - -Why: - -- each user has a user-scoped kubeconfig -- each user operates in a separate namespace -- the app should never perform cluster operations without user scoping - -Kubernetes resources currently managed per project: - -- one sandbox StatefulSet -- one sandbox Service -- three sandbox Ingresses -- optional PostgreSQL cluster through KubeBlocks - -## GitHub Integration - -Primary files: - -- `lib/actions/github.ts` -- `lib/services/github-app.ts` -- `app/api/github/app/callback/route.ts` - -The system uses GitHub App installations, not anonymous repository access. - -Import flow: - -1. user installs GitHub App -2. installation is recorded in `GitHubAppInstallation` -3. user chooses a repository in the import dialog -4. import action verifies repository access against the installation -5. project creation creates a clone task -6. task executor clones the repo into the sandbox using an installation token - -## Design Rules - -### Non-blocking control plane - -User-facing endpoints should write desired state and return. They should not block on Kubernetes creation or long sandbox operations. - -### State machines over ad hoc branching - -If the system needs to resume work later, represent that as persisted state instead of in-memory flags. - -### Resource plane and task plane stay separate - -Use resource states for infrastructure lifecycle. -Use project tasks for asynchronous work that runs on top of ready infrastructure. - -### Polling is the source of truth - -Event-triggered wake-ups are an optimization. Reconcile jobs remain the primary correctness mechanism. - -## Current End-to-End Flows - -### New project - -```text -Create project --> Project.status = CREATING --> Sandbox.status = CREATING --> sandbox reconcile creates and starts sandbox --> Sandbox.status = RUNNING --> Project.status aggregates to RUNNING -``` - -### Import from GitHub - -```text -Import from GitHub --> Project + Sandbox created --> ProjectTask(CLONE_REPOSITORY) created --> sandbox reconcile drives sandbox to RUNNING --> project task reconcile runs clone executor --> task becomes SUCCEEDED or FAILED --> project remains usable regardless of task outcome -``` - -### Add database - -```text -Create database --> Database.status = CREATING --> database reconcile creates cluster --> Database.status = STARTING --> credentials become available --> Database.status = RUNNING -``` - -### Deploy project - -Planned path: - -```text -User requests deploy --> create ProjectTask(type=DEPLOY_PROJECT) --> task reconcile waits for prerequisites --> deploy executor performs deployment work -``` - -## When To Update This Document - -Update this document whenever one of the following changes: - -- a new persisted state machine is introduced -- resource lifecycle semantics change -- a new task type is added -- ownership of a subsystem moves to a different directory -- project-level work stops being sandbox-based diff --git a/docs/design-system.md b/docs/design-system.md deleted file mode 100644 index d964cab..0000000 --- a/docs/design-system.md +++ /dev/null @@ -1,76 +0,0 @@ -# Design System - -## Colors - -Use the `.modern` theme defined in `globals.css`. - -| Usage | CSS Variable | Value | -|-------|--------------|-------| -| Sidebar background | `--sidebar` | #18181b | -| Main background | `--background` | #09090b | -| Primary | `--primary` | #3b82f6 | -| Main text | `--foreground` | #fafafa | -| Muted text | `--muted-foreground` | #a1a1aa | -| Border | `--border` | #27272a | -| Hover/Accent | `--accent` | #27272a | - -## Typography - -| Usage | Font | Fallback | -|-------|------|----------| -| Headings | Space Grotesk | sans-serif | -| Body | Noto Sans | sans-serif | -| Code / Terminal | JetBrains Mono | monospace | - -Font weights: -- Headings: 500-700 -- Body: 400-500 -- Code: 400 - -### Font Application Guidelines - -Apply **Space Grotesk** (`font-[family-name:var(--font-heading)]`) for: -- Page headings (h1-h6) -- Sidebar titles and navigation items -- Search bar placeholder text -- Primary CTA buttons (e.g., "New Project") -- Important interactive elements - -Apply **Noto Sans** (default, no extra class needed) for: -- Body text and descriptions -- Secondary buttons (e.g., "Import") -- Form labels and helper text -- General UI text - -Apply **JetBrains Mono** (`font-[family-name:var(--font-mono)]` or `font-mono`) for: -- Code snippets -- Terminal output -- Keyboard shortcuts (e.g., "⌘K") -- Technical identifiers - -## Components - -Always use shadcn/ui components instead of native HTML elements. - -## Full Screen Dialog - -Use `FullScreenDialog` from `@/components/ui/fullscreen-dialog` for modal dialogs (confirmations, important actions, destructive operations, etc.). - -### Available Components - -| Component | Purpose | -|-----------|---------| -| `FullScreenDialogContent` | Modal container | -| `FullScreenDialogHeader` | Title + description area | -| `FullScreenDialogTitle` | Main heading | -| `FullScreenDialogDescription` | Helper text | -| `FullScreenDialogFooter` | Action buttons area | -| `FullScreenDialogClose` | Cancel/close button | -| `FullScreenDialogAction` | Primary action button | - -### Action Button Variants - -| Variant | Usage | -|---------|-------| -| `default` | Standard primary actions | -| `destructive` | Dangerous/irreversible actions (red style) | diff --git a/docs/development.md b/docs/development.md deleted file mode 100644 index b4275ae..0000000 --- a/docs/development.md +++ /dev/null @@ -1,335 +0,0 @@ -# Development Guide - -This document provides guidance for local development and code patterns. - -## Table of Contents - -- [Prerequisites](#prerequisites) -- [Getting Started](#getting-started) -- [Development Commands](#development-commands) -- [Project Structure](#project-structure) -- [Code Patterns](#code-patterns) -- [Testing](#testing) -- [Debugging](#debugging) - -## Prerequisites - -- **Node.js**: 22.12.0 or higher -- **pnpm**: 9.x or higher -- **PostgreSQL**: 14.x or higher -- **Kubernetes cluster**: For full integration testing (optional) - -## Getting Started - -### 1. Clone and Install - -```bash -git clone https://github.com/FullAgent/fulling.git -cd fulling -pnpm install -``` - -### 2. Environment Setup - -Create `.env.local` file: - -```env -# Database -DATABASE_URL="postgresql://user:password@localhost:5432/fullstackagent" - -# NextAuth -NEXTAUTH_URL="http://localhost:3000" -NEXTAUTH_SECRET="your-nextauth-secret" -AUTH_TRUST_HOST="" - -# GitHub OAuth -GITHUB_CLIENT_ID="your-github-client-id" -GITHUB_CLIENT_SECRET="your-github-client-secret" - -# Sealos OAuth (optional) -SEALOS_JWT_SECRET="" - -# Kubernetes (optional for local dev) -RUNTIME_IMAGE="docker.io/limbo2342/fullstack-web-runtime:sha-ca2470e" - -# Logging -LOG_LEVEL="info" -``` - -### 3. Database Setup - -```bash -npx prisma generate -npx prisma db push -``` - -### 4. Start Development Server - -```bash -pnpm dev -``` - -Open [http://localhost:3000](http://localhost:3000) - -## Development Commands - -### Main Application - -```bash -pnpm dev # Start dev server on 0.0.0.0:3000 -pnpm build # Build for production -pnpm start # Start production server on 0.0.0.0:3000 -pnpm lint # Run ESLint -pnpm lint:fix # Auto-fix ESLint issues -``` - -### Database - -```bash -npx prisma generate # Generate Prisma client after schema changes -npx prisma db push # Push schema to database (dev) -npx prisma studio # Open database GUI -npx prisma migrate dev --name description # Create migration -``` - -### Runtime Image - -```bash -cd runtime -./build.sh # Build Docker image locally -./push-to-dockerhub.sh # Push to registry -``` - -## Project Structure - -``` -fulling/ -├── app/ # Next.js App Router -│ ├── api/ # API routes -│ │ ├── projects/ # Project management -│ │ ├── sandbox/ # Sandbox operations -│ │ ├── user/ # User configuration -│ │ └── github/ # GitHub integration -│ ├── (landing)/ # Landing page -│ └── layout.tsx # Root layout -│ -├── components/ # React components -│ ├── ui/ # Shadcn/UI components -│ └── terminal/ # Terminal components -│ -├── lib/ # Core libraries -│ ├── k8s/ # Kubernetes managers -│ ├── events/ # Event system -│ ├── jobs/ # Background jobs -│ ├── repo/ # Repository layer -│ ├── services/ # Business services -│ └── util/ # Utilities -│ -├── prisma/ # Database schema -│ └── schema.prisma -│ -├── runtime/ # Docker image for sandboxes -│ ├── Dockerfile -│ ├── entrypoint.sh -│ └── ttyd-startup.sh -│ -└── docs/ # Documentation -``` - -## Code Patterns - -### User-Specific K8s Service - -**Always use this pattern** (never instantiate KubernetesService directly): - -```typescript -import { getK8sServiceForUser } from '@/lib/k8s/k8s-service-helper' - -// In API routes or event listeners -const k8sService = await getK8sServiceForUser(userId) - -// Get managers -const sandboxManager = k8sService.getSandboxManager() -const databaseManager = k8sService.getDatabaseManager() -const namespace = k8sService.getDefaultNamespace() -``` - -### Non-Blocking API Endpoints - -API endpoints should only update database, never wait for K8s operations: - -```typescript -// ❌ BAD (blocking) -export async function POST(req: Request) { - await k8sService.createSandbox() // Blocks for 30s - return NextResponse.json({ success: true }) -} - -// ✅ GOOD (non-blocking) -export async function POST(req: Request) { - await prisma.sandbox.create({ - data: { status: 'CREATING', /* ... */ } - }) - // Reconciliation will handle K8s operations - return NextResponse.json({ success: true }) -} -``` - -### Event Listeners - -```typescript -// lib/events/sandbox/sandboxListener.ts - -async function handleCreateSandbox(payload: SandboxEventPayload): Promise { - const { user, project, sandbox } = payload - - if (sandbox.status !== 'CREATING') return - - try { - const k8sService = await getK8sServiceForUser(user.id) - await k8sService.getSandboxManager().createSandbox({...}) - await updateSandboxStatus(sandbox.id, 'STARTING') - await projectStatusReconcile(project.id) - } catch (error) { - logger.error(`Failed to create sandbox: ${error}`) - await updateSandboxStatus(sandbox.id, 'ERROR') - } -} -``` - -### Error Handling - -```typescript -try { - await k8sOperation() - await updateStatus('RUNNING') -} catch (error) { - logger.error(`Operation failed: ${error}`) - await updateStatus('ERROR') - // Don't throw - let reconciliation retry -} -``` - -### Project Name Sanitization - -```typescript -import { KubernetesUtils } from '@/lib/k8s/kubernetes-utils' - -const k8sProjectName = KubernetesUtils.toK8sProjectName(projectName) -// Converts "My Blog!" → "myblog" (lowercase, alphanumeric, max 20 chars) -``` - -## Testing - -### Local Testing - -```bash -# 1. Start development server -pnpm dev - -# 2. Create project via UI -# Open http://localhost:3000 and login - -# 3. Check database -npx prisma studio -``` - -### Integration Testing - -For full integration testing with Kubernetes: - -1. Set up a Kubernetes cluster (e.g., Sealos, minikube) -2. Configure kubeconfig in UserConfig table -3. Test project creation flow - -## Debugging - -### Database Queries - -```typescript -// Enable Prisma query logging -const prisma = new PrismaClient({ - log: ['query', 'info', 'warn', 'error'], -}) -``` - -### Kubernetes Operations - -```bash -# Set kubeconfig -export KUBECONFIG=/path/to/kubeconfig - -# Check StatefulSets -kubectl get statefulsets -n {namespace} - -# Check pods -kubectl get pods -n {namespace} -l app={statefulset-name} - -# Pod logs -kubectl logs -n {namespace} {pod-name} - -# Check KubeBlocks database -kubectl get cluster -n {namespace} - -# Check ingresses -kubectl get ingress -n {namespace} -``` - -### Reconciliation Jobs - -Check logs for reconciliation job execution: - -```typescript -// Look for these log patterns: -// "Reconciliation job started" -// "Processing sandbox: {id}" -// "Sandbox {id} status changed to {status}" -``` - -### Event System - -```typescript -// Enable event bus debug logging -import { sandboxEventBus } from '@/lib/events/sandbox/bus' - -sandboxEventBus.on('CreateSandbox', (payload) => { - console.log('CreateSandbox event received:', payload) -}) -``` - -## Code Style - -### TypeScript - -- Use TypeScript strict mode -- Prefer type inference over explicit types -- Use `const` assertions for literal types - -### React - -- Use functional components with hooks -- Prefer composition over inheritance -- Use Shadcn/UI components - -### Naming Conventions - -| Type | Convention | Example | -|------|------------|---------| -| Files | kebab-case | `sandbox-manager.ts` | -| Components | PascalCase | `TerminalContainer.tsx` | -| Functions | camelCase | `getK8sServiceForUser` | -| Constants | UPPER_SNAKE_CASE | `DATABASE_URL` | -| Types | PascalCase | `SandboxStatus` | -| Enums | PascalCase | `ProjectStatus` | - -### No Comments Policy - -**Important**: Do not add comments unless explicitly requested. Code should be self-documenting through clear naming and structure. - -## Related Documentation - -- [Architecture](./architecture.md) - Reconciliation pattern and event system -- [API Reference](./api.md) - API endpoints and request/response formats -- [Database Schema](./database.md) - Prisma models and relationships -- [Operations Manual](./operations.md) - Deployment and K8s operations -- [Troubleshooting](./troubleshooting.md) - Common issues and debugging diff --git a/docs/operations.md b/docs/operations.md deleted file mode 100644 index e818172..0000000 --- a/docs/operations.md +++ /dev/null @@ -1,328 +0,0 @@ -# Operations Manual - -This document provides guidance for deployment and Kubernetes operations. - -## Table of Contents - -- [Deployment](#deployment) -- [Kubernetes Resources](#kubernetes-resources) -- [Resource Management](#resource-management) -- [Monitoring](#monitoring) -- [Backup and Recovery](#backup-and-recovery) - -## Deployment - -### Prerequisites - -- Kubernetes cluster with KubeBlocks installed -- Sealos platform (recommended) or any K8s cluster -- Container registry access - -### Environment Variables - -Required environment variables for production: - -```env -# Database -DATABASE_URL="postgresql://..." - -# NextAuth -NEXTAUTH_URL="https://your-domain.com" -NEXTAUTH_SECRET="your-secret" -AUTH_TRUST_HOST="true" - -# GitHub OAuth -GITHUB_CLIENT_ID="..." -GITHUB_CLIENT_SECRET="..." - -# GitHub App (optional) -GITHUB_APP_ID="..." -GITHUB_APP_PRIVATE_KEY="..." - -# Sealos OAuth -SEALOS_JWT_SECRET="..." - -# Kubernetes -RUNTIME_IMAGE="docker.io/limbo2342/fullstack-web-runtime:sha-ca2470e" - -# Aiproxy (optional) -AIPROXY_ENDPOINT="..." -ANTHROPIC_BASE_URL="..." - -# Logging -LOG_LEVEL="info" -``` - -### Deploy to Sealos - -1. Build and push Docker image: -```bash -docker build -t your-registry/fulling:latest . -docker push your-registry/fulling:latest -``` - -2. Create Sealos application with the image -3. Configure environment variables -4. Set up domain and SSL - -### Resource Requirements - -**Main Application**: -- CPU: 500m request, 2000m limit -- Memory: 512Mi request, 2Gi limit - -**Per Sandbox**: -- CPU: 20m request, 2000m limit -- Memory: 25Mi request, 4096Mi limit -- Storage: 10Gi PVC - -**Per Database**: -- CPU: 100m request, 1000m limit -- Memory: 102Mi request, 1024Mi limit -- Storage: 3Gi PVC - -## Kubernetes Resources - -### Per Project Resources - -Each project creates the following K8s resources: - -**1. StatefulSet (Sandbox)**: -- Name: `{project-name}-{random-6chars}` -- Image: `limbo2342/fullstack-web-runtime:sha-ca2470e` -- Ports: 3000 (app), 7681 (ttyd), 8080 (filebrowser) -- PVC: 10Gi (persistent storage) - -**2. Service**: -- Name: `{sandbox-name}-service` -- Ports: 3000, 7681, 8080 - -**3. Ingresses**: -- App: `{sandbox-name}-app.{domain}` → 3000 -- Terminal: `{sandbox-name}-ttyd.{domain}` → 7681 -- FileBrowser: `{sandbox-name}-filebrowser.{domain}` → 8080 - -**4. PostgreSQL Cluster (KubeBlocks)**: -- Name: `{project-name}-{random-6chars}` -- Version: postgresql-14.8.0 -- Storage: 3Gi - -### Labels - -All resources have these labels: - -```yaml -labels: - cloud.sealos.io/app-deploy-manager: {resource-name} - project.fullstackagent.io/name: {k8s-project-name} - app: {resource-name} -``` - -## Resource Management - -### Start/Stop Projects - -**Stop** (scales replicas to 0): -```bash -# Via API -POST /api/projects/{id}/stop - -# Via kubectl -kubectl scale statefulset {sandbox-name} --replicas=0 -n {namespace} -``` - -**Start** (scales replicas to 1): -```bash -# Via API -POST /api/projects/{id}/start - -# Via kubectl -kubectl scale statefulset {sandbox-name} --replicas=1 -n {namespace} -``` - -### Delete Projects - -```bash -# Via API (soft delete) -POST /api/projects/{id}/delete - -# Via kubectl (hard delete) -kubectl delete statefulset {sandbox-name} -n {namespace} -kubectl delete service {sandbox-name}-service -n {namespace} -kubectl delete ingress {sandbox-name}-app-ingress -n {namespace} -kubectl delete ingress {sandbox-name}-ttyd-ingress -n {namespace} -kubectl delete ingress {sandbox-name}-filebrowser-ingress -n {namespace} -kubectl delete pvc data-{sandbox-name}-0 -n {namespace} -``` - -### Update Runtime Image - -1. Build and push new image: -```bash -cd runtime -./build.sh -./push-to-dockerhub.sh -``` - -2. Update `lib/k8s/versions.ts`: -```typescript -export const VERSIONS = { - RUNTIME_IMAGE: 'docker.io/limbo2342/fullstack-web-runtime:sha-new', - // ... -} -``` - -3. Restart main app to pick up new version - -4. Existing sandboxes will use new image on next restart - -## Monitoring - -### Health Checks - -**Main Application**: -```bash -curl https://your-domain.com/api/health -``` - -**Sandbox Containers**: -```bash -kubectl get pods -n {namespace} -l app={sandbox-name} -``` - -### Logs - -**Main Application**: -```bash -# Sealos -sealos logs {app-name} - -# kubectl -kubectl logs -n {namespace} {pod-name} -``` - -**Sandbox Containers**: -```bash -kubectl logs -n {namespace} {sandbox-pod-name} - -# ttyd logs -kubectl logs -n {namespace} {sandbox-pod-name} | grep ttyd -``` - -### Metrics - -Key metrics to monitor: -- API response time (< 100ms) -- Reconciliation job duration (< 5s) -- Database connections -- K8s API calls -- Pod restart count - -## Backup and Recovery - -### Database Backup - -**Main Application Database**: -```bash -pg_dump -h localhost -U user -d fullstackagent > backup.sql -``` - -**Project Databases (KubeBlocks)**: -```bash -# KubeBlocks automatic backup -kubectl get backup -n {namespace} - -# Manual backup -kubectl apply -f - < `IMPORTING` -- `SUCCEEDED` => `IMPORTED` -- `FAILED` or `CANCELLED` => `IMPORT_FAILED` - -For the current product contract: - -- project status may become `RUNNING` -- import may independently derive to `IMPORT_FAILED` - -The intended current UI meaning is: - -- `RUNNING + IMPORT_FAILED` - -This combination means: - -- the sandbox is available -- the project exists and is usable -- the requested repository import did not complete successfully - -Current implementation note: - -- UI may choose to render this as `Needs Attention` -- `Needs Attention` is a presentation label, not the underlying persisted import status - -## Retry Behavior - -Repository clone should automatically retry up to 3 times, matching the current system behavior. - -Requirements: - -- retries are automatic -- no manual retry UX is required in this phase -- exhausting retries should leave the project intact and mark import as failed - -## Persistence Requirements - -The system must persist enough state to represent: - -- that the project exists -- that the sandbox exists -- that the project was created from GitHub -- that the clone task was attempted -- whether the clone task eventually succeeded or failed - -If clone fails, the database must still clearly reflect: - -- project creation succeeded -- import did not succeed - -This currently means: - -- `Project` persists the imported GitHub metadata -- `ProjectTask` persists clone attempts, final success or failure, and error text -- import status is inferred from task state rather than stored as a dedicated project column - -## GitHub Metadata Requirements - -If the repository later becomes unavailable or permissions change, the project should -continue to retain its GitHub association metadata. - -This means clone failure or later repository access loss should not automatically clear: - -- GitHub installation reference -- GitHub repository ID -- GitHub repository full name -- default branch metadata - -## Non-Goals - -This PRD does not define: - -- a new import intent model -- a dedicated import failure modal -- a new `ProjectStatus` enum value for import outcomes -- post-import repository analysis -- skill installation after import -- deployment after import -- new manual retry workflows -- sandbox startup timeout policy - -## Implementation Notes - -Current implementation should preserve this product contract: - -- request acceptance is synchronous and returns after control-plane state is created -- project creation success is asynchronous and is anchored to sandbox success -- clone failure is visible as an import failure, not as project creation failure -- import logic may fail independently after the project already exists -- import status should be derived from clone task state, not folded into `Project.status` diff --git a/docs/prds/skills/global-skill-enablement-control-flow.md b/docs/prds/skills/global-skill-enablement-control-flow.md deleted file mode 100644 index fb40255..0000000 --- a/docs/prds/skills/global-skill-enablement-control-flow.md +++ /dev/null @@ -1,345 +0,0 @@ -# Global Skill Enablement Control Flow - -Status: Implemented - -## Goal - -Define the product behavior and control-plane semantics for enabling a skill from -the global Skills directory. - -This PRD exists to clarify what "success" means for: - -- request acceptance -- user-level skill enablement -- per-project skill installation -- coverage of existing projects -- coverage of future projects -- install failure handling - -## Scope - -This document covers the current product contract for: - -- enabling a skill globally for a user -- persisting the install command associated with that globally enabled skill -- treating a globally enabled skill as applicable to all of the user's projects -- creating install-skill work for all existing projects owned by that user -- ensuring newly created projects also receive install-skill work -- waiting to execute installation until a project's sandbox is runnable -- representing project-level install failure without disabling the skill globally - -This document does not define: - -- disabling or uninstalling a skill -- skill marketplace or discovery design -- skill version pinning or upgrade policy -- auto-starting stopped projects only to install skills -- detailed project-level skill UI beyond required status semantics - -Current phase note: - -- the skill directory is a static local catalog -- users do not create custom skills from the UI in this phase -- the catalog is not yet backed by a remote marketplace or external source - -## User Intent - -When a user enables a skill from the global Skills tab, the system receives three -requested outcomes: - -1. Mark the skill as enabled for that user at the global level -2. Ensure every existing project owned by that user will install the skill -3. Ensure every future project owned by that user will also install the skill - -These outcomes are related, but they are not treated as a single synchronous -"install everywhere now" success condition. - -In particular: - -- a stopped project does not need to be started immediately -- a project may remain pending installation until its sandbox becomes `RUNNING` -- the global enablement remains valid even if some project installations have not yet completed - -## Success Semantics - -### Request acceptance success - -The enable request is considered accepted when the control plane successfully -persists enough state to guarantee global coverage. - -At minimum, this means: - -- the user-skill global enablement is persisted -- the skill's stable identity and `installCommand` are persisted with that global record -- the system has persisted install intent for all current projects owned by the user -- future project creation can discover that this skill is globally enabled - -The API may return success immediately even though: - -- some projects are currently stopped -- some install tasks have not started yet -- no sandbox-side installation work has completed yet - -### User-level enablement success - -A skill is considered enabled for a user when the global enablement state is -persisted successfully. - -This is the source of truth for whether the skill should apply to the user's -projects. - -User-level enablement success does not require: - -- every existing project to have already finished installation -- any stopped project to be started automatically - -### Existing project coverage success - -An existing project is considered covered by a globally enabled skill when the -control plane has persisted installation work for that project. - -For a currently running project, this normally means an `INSTALL_SKILL` task can -proceed immediately. - -For the current implementation, the control plane should also proactively trigger -task evaluation for projects that are already `RUNNING`, rather than waiting only -for the next periodic reconcile cycle. - -For a stopped or otherwise non-runnable project, this means an `INSTALL_SKILL` -task may remain pending until prerequisites are satisfied. - -### Project installation success - -A project is considered to have the skill available only when installation for -that project succeeds. - -This is independent from user-level enablement success. - -### Future project coverage success - -If a user already has a skill enabled globally, every newly created project must -inherit that desired state automatically. - -This means project creation should also create install work for each globally -enabled skill, even if the new project's sandbox is not yet runnable. - -## Failure Semantics - -### Global enablement persistence failure - -If the system cannot persist the user-level enablement, the request fails. - -The skill must not appear enabled for the user in this case. - -### Existing project fan-out failure - -The system must not silently enable a skill for only some of the user's current -projects. - -If immediate fan-out cannot fully complete during request handling, the system -must still preserve durable control-plane state that guarantees missing projects -will be reconciled later. - -The system must not rely on best-effort in-memory fan-out alone. - -### Project installation failure - -If installation fails for one project, the system must: - -- keep the user-level skill enablement active -- keep the project intact -- keep the sandbox intact -- mark installation as failed for that project -- allow other projects to continue installing independently - -Project installation failure does not disable the skill globally. - -Examples include: - -- sandbox command execution failure -- timeout while installing inside the sandbox -- missing files or incompatible repository structure -- transient access or environment failures inside the sandbox - -### Non-runnable project state - -If a project's sandbox is not `RUNNING`, this is not itself an installation -failure. - -Instead: - -- the install task remains pending or waiting for prerequisites -- the project should not be auto-started solely to satisfy skill installation -- installation should proceed when the sandbox later becomes runnable - -## Status Requirements - -The system should represent three distinct layers of state: - -1. User-level skill enablement state -2. Project resource status -3. Project-level skill installation status - -### User-level skill enablement state - -The system must persist whether a skill is globally enabled for a user. - -This state means: - -- the skill should apply to all current projects -- the skill should apply to all future projects - -This state is not the same as project installation success. - -The user-level skill record must also persist the install command used to apply -that skill to projects. - -### Project resource status - -`Project.status` continues to mean resource lifecycle state only. - -Examples: - -- `CREATING` -- `STARTING` -- `RUNNING` -- `STOPPED` -- `ERROR` - -Skill enablement and skill installation must not be folded into `Project.status`. - -### Project-level skill installation status - -For the current product contract, project-level skill installation status may be -derived from the latest `INSTALL_SKILL` task for a given project and skill. - -Derived meaning: - -- `WAITING_FOR_PREREQUISITES` or `PENDING` => `PENDING_INSTALL` -- `RUNNING` => `INSTALLING` -- `SUCCEEDED` => `INSTALLED` -- `FAILED` or `CANCELLED` => `INSTALL_FAILED` - -This allows valid combinations such as: - -- user skill enabled + project `STOPPED` + project skill `PENDING_INSTALL` -- user skill enabled + project `RUNNING` + project skill `INSTALL_FAILED` -- user skill enabled + project `RUNNING` + project skill `INSTALLED` - -## UI Requirements - -For the current phase of the product: - -- the global Skills tab should reflect user-level enablement state -- the global Skills tab is backed by a static local catalog in this phase -- enabling a skill should return quickly after durable state is created -- the UI should not wait for all projects to finish installation before showing the skill as enabled -- stopped projects should not be presented as immediate installation failures -- if surfaced later, project-level UI should distinguish `PENDING_INSTALL` from `INSTALL_FAILED` - -No dedicated bulk progress dashboard is required in this phase. - -## Retry Behavior - -Skill installation should automatically retry up to 3 times, matching the current -project task retry model. - -Requirements: - -- retries are automatic -- waiting for sandbox prerequisites is not counted as terminal failure -- exhausting retries should leave the global skill enablement intact -- no manual retry UX is required in this phase - -## Persistence Requirements - -The system must persist enough state to represent: - -- that a skill is globally enabled for a user -- the stable identity of that skill -- that every current project has installation intent -- that future projects can inherit globally enabled skills -- whether installation for a given project eventually succeeded or failed - -This currently implies the need for: - -- a persisted `UserSkill` record -- stable `UserSkill` fields that include at least: - - `userId` - - `skillId` - - `installCommand` -- `ProjectTask` records for project-level install attempts -- task payload that includes at least: - - `userSkillId` - - `skillId` - - `installCommand` -- task result or error data that records project-level install outcome - -If installation fails for one project, the database must still clearly reflect: - -- the skill remains enabled globally for the user -- the affected project has not yet installed it successfully -- other projects are unaffected by that single-project failure - -## Skill Identity Requirements - -Global enablement and project installation records must reference a stable skill -identity rather than only a display label. - -This means: - -- renaming a skill in the catalog must not orphan existing enablement state -- project install tasks must remain attributable to the intended skill -- install failure must not erase the user-to-skill association - -## Install Command Requirements - -Each globally enabled skill must persist an `installCommand`. - -For the current phase of the product: - -- `installCommand` is part of the user-level enabled-skill record -- project install tasks should receive `installCommand` in their payload -- install execution should consume the command from task payload rather than - inferring installation behavior dynamically at runtime -- `installCommand` must be non-interactive so sandbox execution can complete - without user input -- the current phase does not define editing an existing enabled skill's - `installCommand` - -## Non-Goals - -This PRD does not define: - -- uninstall or disable behavior -- skill marketplace ranking, browsing, or recommendation logic -- version selection, upgrade rollout, or migration policy -- automatic starting of stopped sandboxes for install purposes -- manual retry controls -- organization-scoped or team-scoped skill enablement -- post-install analysis or deployment behavior triggered by the skill - -## Implementation Notes - -Current implementation should preserve this product contract: - -- the user action enables a skill at the global user scope, not the single-project scope -- the current Skills page reads from a static local catalog, not a user-authored - or remote marketplace-backed catalog -- `UserSkill` is the durable source of truth for the enabled skill and its `installCommand` -- project installation is asynchronous and should run through `ProjectTask` -- each `INSTALL_SKILL` task should contain an execution snapshot of the `installCommand` -- install execution should happen only when a project's sandbox is `RUNNING` -- `installCommand` should be written in non-interactive form -- sandbox lifecycle state remains separate from skill installation state -- future project creation should consult globally enabled skills and create install work automatically -- already-`RUNNING` projects should be triggered immediately after enablement so - installation does not rely only on cron pickup - -Current codebase note: - -- `UserSkill` persistence and `INSTALL_SKILL` fan-out are implemented -- task prerequisite evaluation matches the desired sandbox `RUNNING` gate -- import projects additionally wait for successful repository clone before skill installation -- the current catalog entry uses a non-interactive command form: - `npx -y skills add https://github.com/anthropics/skills --skill frontend-design -y` diff --git a/docs/prds/skills/uninstall-skill-control-flow.md b/docs/prds/skills/uninstall-skill-control-flow.md deleted file mode 100644 index c6be4a6..0000000 --- a/docs/prds/skills/uninstall-skill-control-flow.md +++ /dev/null @@ -1,383 +0,0 @@ -# Uninstall Skill Control Flow - -Status: Implemented - -## Goal - -Define the product behavior and control-plane semantics for uninstalling a skill -from the global Skills directory. - -In this product, uninstall is the user-facing operation for removing a globally -enabled skill. There is no separate disable behavior. - -This PRD exists to clarify what "success" means for: - -- request acceptance -- user-level skill removal -- per-project skill uninstallation -- removal of future project inheritance -- handling projects that are not currently runnable -- uninstall failure handling - -## Scope - -This document covers the current product contract for: - -- uninstalling a skill globally for a user -- treating global uninstall as removal of the skill from all existing projects -- ensuring future projects no longer inherit that skill -- removing the globally persisted skill record that includes that skill's `installCommand` -- creating uninstall-skill work for projects that must converge to "not installed" -- preventing stale install intent from re-applying the skill after uninstall -- waiting to execute sandbox-side uninstall until a project's sandbox is runnable - -This document does not define: - -- a separate disable workflow -- skill catalog or marketplace design -- skill version rollback policy -- auto-starting stopped projects only to uninstall skills -- bulk operator tooling for failed uninstalls - -Current phase note: - -- the skill directory is a static local catalog -- users do not create custom skills from the UI in this phase -- the catalog is not yet backed by a remote marketplace or external source - -## User Intent - -When a user uninstalls a skill from the global Skills tab, the system receives -three requested outcomes: - -1. Remove the skill from the user's global desired state -2. Ensure every existing project owned by that user converges to "skill not installed" -3. Ensure future projects owned by that user no longer inherit that skill - -These outcomes are related, but they are not treated as a single synchronous -"remove it everywhere right now" success condition. - -In particular: - -- a stopped project does not need to be started immediately -- a project may remain pending uninstall until its sandbox becomes `RUNNING` -- a global uninstall remains valid even if some project uninstalls have not yet completed - -## Success Semantics - -### Request acceptance success - -The uninstall request is considered accepted when the control plane successfully -persists enough state to guarantee future convergence to "not installed." - -At minimum, this means: - -- the user-level global enablement for that skill is removed or marked inactive -- the persisted `installCommand` associated with that user-level skill record is removed - together with that global record -- future project creation can discover that the skill is no longer globally enabled -- the system has persisted uninstall intent or equivalent convergence state for - current projects that still need removal - -The API may return success immediately even though: - -- some projects are currently stopped -- some uninstall tasks have not started yet -- some running projects have not yet finished uninstalling inside the sandbox - -### User-level uninstall success - -A skill is considered uninstalled for a user when the global enablement state is -removed successfully. - -This is the source of truth for whether the skill should still apply to the -user's projects. - -User-level uninstall success does not require: - -- every existing project to have already finished uninstalling -- any stopped project to be started automatically - -### Existing project coverage success - -An existing project is considered covered by a global uninstall when the control -plane has persisted enough state to ensure that the project will not retain the -skill indefinitely. - -For a currently running project, this normally means an `UNINSTALL_SKILL` task -can proceed immediately. - -For the current implementation, the control plane should also proactively trigger -task evaluation for projects that are already `RUNNING`, rather than waiting only -for the next periodic reconcile cycle. - -For a stopped or otherwise non-runnable project, this means uninstall work may -remain pending until prerequisites are satisfied. - -For a project that never successfully installed the skill, coverage success may -be satisfied by cancelling stale install intent or recording that no uninstall -work is required. - -### Project uninstall success - -A project is considered to have completed uninstall only when the skill is no -longer present for that project. - -This may happen through: - -- successful execution of an `UNINSTALL_SKILL` task -- successful cancellation or invalidation of stale install intent before the - skill was ever installed -- a no-op terminal outcome for a project that already does not have the skill - -Project uninstall success is independent from user-level uninstall success. - -### Future project removal success - -If a user has uninstalled a skill globally, newly created projects must not -receive install work for that skill. - -This means future project creation must consult the latest user-level desired -state rather than historical install attempts. - -## Failure Semantics - -### Global uninstall persistence failure - -If the system cannot remove the user-level enablement state, the request fails. - -The skill must continue to appear enabled for the user in this case. - -### Existing project fan-out failure - -The system must not silently remove a skill for only some of the user's current -projects while leaving others with no durable convergence path. - -If immediate fan-out cannot fully complete during request handling, the system -must still preserve durable control-plane state that guarantees missing projects -will be reconciled later. - -The system must not rely on best-effort in-memory fan-out alone. - -### Project uninstall failure - -If uninstall fails for one project, the system must: - -- keep the global uninstall in effect for the user -- keep the project intact -- keep the sandbox intact -- mark uninstall as failed for that project -- allow other projects to continue uninstalling independently - -Project uninstall failure does not re-enable the skill globally. - -Examples include: - -- sandbox command execution failure -- timeout while uninstalling inside the sandbox -- repository state that prevents safe removal -- transient access or environment failures inside the sandbox - -### Non-runnable project state - -If a project's sandbox is not `RUNNING`, this is not itself an uninstall -failure. - -Instead: - -- the uninstall task remains pending or waiting for prerequisites -- the project should not be auto-started solely to satisfy skill uninstall -- uninstall should proceed when the sandbox later becomes runnable - -### Stale install intent after uninstall - -Once a global uninstall request is accepted, older install intent must not later -re-apply the skill. - -This means the system must ensure that: - -- pending `INSTALL_SKILL` work for that same skill does not win over the newer - uninstall intent -- a project does not end in `INSTALLED` solely because an older install task ran - after the user already uninstalled the skill globally - -The latest desired state must win. - -## Status Requirements - -The system should represent three distinct layers of state: - -1. User-level skill desired state -2. Project resource status -3. Project-level skill uninstall status - -### User-level skill desired state - -The system must persist whether a skill is currently globally enabled for a user. - -After uninstall is accepted, this state means: - -- the skill no longer applies to current projects as desired state -- the skill no longer applies to future projects - -This state is not the same as project uninstall completion. - -Before uninstall, the user-level skill record is also the source of truth for -that skill's `installCommand`. - -### Project resource status - -`Project.status` continues to mean resource lifecycle state only. - -Examples: - -- `RUNNING` -- `STOPPED` -- `STARTING` -- `ERROR` - -Skill uninstall must not be folded into `Project.status`. - -### Project-level skill uninstall status - -For the current product contract, project-level uninstall status may be derived -from the latest effective task or terminal no-op outcome for a given project and -skill. - -Derived meaning: - -- `WAITING_FOR_PREREQUISITES` or `PENDING` => `PENDING_UNINSTALL` -- `RUNNING` => `UNINSTALLING` -- `SUCCEEDED` => `UNINSTALLED` -- `FAILED` or `CANCELLED` => `UNINSTALL_FAILED` - -This allows valid combinations such as: - -- user skill uninstalled + project `STOPPED` + project skill `PENDING_UNINSTALL` -- user skill uninstalled + project `RUNNING` + project skill `UNINSTALL_FAILED` -- user skill uninstalled + project `RUNNING` + project skill `UNINSTALLED` - -## UI Requirements - -For the current phase of the product: - -- the global Skills tab should stop showing the skill as enabled once durable - uninstall state is created -- the global Skills tab is backed by a static local catalog in this phase -- the UI should not wait for all projects to finish uninstall before reflecting - the global uninstall -- stopped projects should not be presented as immediate uninstall failures -- if surfaced later, project-level UI should distinguish `PENDING_UNINSTALL` - from `UNINSTALL_FAILED` - -No dedicated bulk progress dashboard is required in this phase. - -## Retry Behavior - -Skill uninstall should automatically retry up to 3 times, matching the current -project task retry model. - -Requirements: - -- retries are automatic -- waiting for sandbox prerequisites is not counted as terminal failure -- exhausting retries should leave the global uninstall in effect -- no manual retry UX is required in this phase - -## Persistence Requirements - -The system must persist enough state to represent: - -- that the skill is no longer globally enabled for the user -- the stable identity of that skill -- that current projects have uninstall intent or equivalent no-op resolution -- that future projects no longer inherit the skill -- whether uninstall for a given project eventually succeeded or failed - -This currently implies the need for: - -- a persisted `UserSkill` record that can be removed or marked inactive -- stable `UserSkill` fields that include at least: - - `userId` - - `skillId` - - `installCommand` -- `ProjectTask` records for project-level uninstall attempts when removal work is needed -- a way to invalidate or supersede stale install intent for the same skill -- task payload that includes at least: - - `userSkillId` - - `skillId` - - `installCommand` - - `uninstallCommand` -- task result or error data that records project-level uninstall outcome - -If uninstall fails for one project, the database must still clearly reflect: - -- the skill is no longer globally enabled for the user -- the affected project has not yet completed uninstall successfully -- future projects should not inherit that skill - -## Skill Identity Requirements - -Global uninstall and project uninstall records must reference a stable skill -identity rather than only a display label. - -This means: - -- renaming a skill in the catalog must not orphan uninstall state -- project uninstall tasks must remain attributable to the intended skill -- stale install and newer uninstall intent can be compared for the same skill identity - -## Install Command Requirements - -For the current phase of the product: - -- each globally enabled skill is represented by a `UserSkill` record that includes - `installCommand` -- the static catalog also defines the `uninstallCommand` used for removal -- uninstall removes that global `UserSkill` desired state -- historical uninstall and install tasks may still retain `installCommand` in their - payload as execution snapshots -- uninstall tasks should receive `uninstallCommand` in their payload as an execution snapshot -- uninstall execution should rely on task payload and task semantics rather than - attempting to rebuild prior install intent from mutable runtime state - -## Non-Goals - -This PRD does not define: - -- a separate disable behavior -- skill version downgrade or rollback policy -- automatic starting of stopped sandboxes for uninstall purposes -- manual retry controls -- organization-scoped or team-scoped skill policies -- UI for bulk project-by-project repair operations -- deletion of historical task records - -## Implementation Notes - -Current implementation should preserve this product contract: - -- the user action removes a skill at the global user scope, not the single-project scope -- the current Skills page reads from a static local catalog, not a user-authored - or remote marketplace-backed catalog -- the removed global skill record is the `UserSkill` source of truth that previously - held the skill's `installCommand` -- project uninstall is asynchronous and should run through `ProjectTask` -- uninstall and stale install resolution should operate on tasks that retain - `installCommand` snapshots in payload -- uninstall execution should happen only when a project's sandbox is `RUNNING` -- sandbox lifecycle state remains separate from skill uninstall state -- future project creation should consult the latest global desired state and not - recreate install work for an uninstalled skill -- stale install work must not override a newer global uninstall - -Current codebase note: - -- `ProjectTaskType` already reserves `INSTALL_SKILL` and `UNINSTALL_SKILL` -- enable-side `UserSkill` persistence and `INSTALL_SKILL` fan-out are implemented -- task prerequisite evaluation already matches the desired sandbox `RUNNING` gate -- global uninstall removes the `UserSkill` desired state and fans out uninstall work -- pending and waiting install tasks for the same skill are cancelled when uninstall is accepted -- stale install work is prevented from winning over newer uninstall intent during task reconcile -- projects that are already `RUNNING` are triggered immediately after uninstall fan-out -- uninstall executor, uninstall UI entry, and global uninstall control flow are implemented diff --git a/docs/releases/v2.0.0.md b/docs/releases/v2.0.0.md deleted file mode 100644 index c85775e..0000000 --- a/docs/releases/v2.0.0.md +++ /dev/null @@ -1,57 +0,0 @@ -# Fulling v2.0.0 - -Fulling v2.0.0 is the release snapshot for the v2 control-plane work. - -This release is not intended to claim the full future platform surface. It -stabilizes the current agentic sandbox foundation so `main` can move on to 3.0 -development with a clean historical branch. - -## Release Positioning - -Fulling v2 is a browser-based AI development environment with: - -- project creation from scratch -- GitHub App based repository import -- isolated Kubernetes sandboxes -- browser terminal and file manager access -- live HTTPS runtime URLs -- optional PostgreSQL databases -- persisted background reconciliation for resource and project-task work -- global skill enablement and uninstall fan-out through `ProjectTask` - -## Not 2.0 Scope - -The following surfaces remain future product work and are intentionally not -presented as complete v2.0 features: - -- MCP management -- template marketplace -- top-level integrations marketplace -- fully automated production deployment -- generalized deploy providers - -The codebase contains early hooks and placeholders for some of these ideas, but -they should be treated as 3.0 development inputs rather than 2.0 guarantees. - -## Key Changes Since v1.0 - -- Replaced the older GitHub OAuth repository flow with GitHub App installation - metadata and installation-token based import. -- Made PostgreSQL optional, so projects can start with only a sandbox. -- Added `ProjectTask` as the task-plane abstraction for asynchronous project - work such as repository clone and skill operations. -- Added global skill enablement and uninstall semantics backed by durable - per-project install/uninstall tasks. -- Introduced `lib/platform/` as the first step toward explicit control-plane - boundaries. -- Updated documentation around the control plane, reconciliation model, and - intended architecture evolution. - -## Branch and Tag Plan - -- Release branch: `release/2.0` -- Release tag: `v2.0.0` -- Ongoing development branch: `main` for 3.0 work - -Use `release/2.0` for v2 stabilization fixes only. Use `main` for product and -architecture changes that belong to the next major version. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md deleted file mode 100644 index 0eb338a..0000000 --- a/docs/troubleshooting.md +++ /dev/null @@ -1,312 +0,0 @@ -# Troubleshooting Guide - -This document provides solutions for common issues and debugging techniques. - -## Table of Contents - -- [Common Issues](#common-issues) -- [Debugging Commands](#debugging-commands) -- [Error Messages](#error-messages) -- [Performance Issues](#performance-issues) -- [Incident References](#incident-references) - -## Common Issues - -### Issue 1: API Endpoints Must Not Block - -**Symptom**: API endpoint takes 30+ seconds to respond - -**Cause**: API is directly executing K8s operations instead of using reconciliation - -**Solution**: API should only update database, return immediately - -```typescript -// ❌ BAD (blocking) -export async function POST(req: Request) { - await k8sService.createSandbox() // Blocks for 30s - return NextResponse.json({ success: true }) -} - -// ✅ GOOD (non-blocking) -export async function POST(req: Request) { - await prisma.sandbox.create({ - data: { status: 'CREATING', /* ... */ } - }) - // Reconciliation will handle K8s operations - return NextResponse.json({ success: true }) -} -``` - -### Issue 2: Always Use getK8sServiceForUser() - -**Symptom**: "User does not have KUBECONFIG configured" - -**Cause**: Trying to use global K8s service instead of user-specific - -**Solution**: Always load user's kubeconfig from UserConfig table - -```typescript -// ❌ BAD (old pattern) -const k8sService = new KubernetesService() - -// ✅ GOOD (v0.4.0+) -const k8sService = await getK8sServiceForUser(userId) -``` - -### Issue 3: Optimistic Locking Prevents Concurrent Updates - -**Symptom**: Reconciliation job skips some records - -**Cause**: Multiple instances or rapid cycles trying to process same records - -**Solution**: This is expected behavior - optimistic locking ensures single-writer - -```typescript -// Repository layer automatically handles locking -const lockedSandboxes = await acquireAndLockSandboxes(10) -// Only returns sandboxes where lockedUntil IS NULL OR < NOW() -// Sets lockedUntil = NOW() + 30 seconds atomically -``` - -### Issue 4: Status Aggregation Rules - -**Symptom**: Project shows PARTIAL status unexpectedly - -**Cause**: Child resources in inconsistent states - -**Solution**: Understand aggregation priority rules - -**Priority order**: -1. **ERROR** - At least one resource has ERROR -2. **CREATING** - At least one resource has CREATING -3. **UPDATING** - At least one resource has UPDATING -4. **Pure states** - All same status → use that status -5. **Transition states**: - - STARTING: All ∈ {RUNNING, STARTING} - - STOPPING: All ∈ {STOPPED, STOPPING} - - TERMINATING: All ∈ {TERMINATED, TERMINATING} -6. **PARTIAL** - Inconsistent mixed states - -### Issue 5: ttyd Authentication Failed - -**Symptom**: Terminal shows "Authentication failed" - -**Cause**: Missing or incorrect TTYD_ACCESS_TOKEN - -**Solution**: Check environment variable - -```bash -# In sandbox pod -echo $TTYD_ACCESS_TOKEN - -# Check URL format -# Should be: https://{domain}?authorization={base64(user:token)} -``` - -### Issue 6: FileBrowser Login Failed - -**Symptom**: FileBrowser shows "Invalid credentials" - -**Cause**: Missing or incorrect FILE_BROWSER_USERNAME/PASSWORD - -**Solution**: Check environment variables - -```bash -# In sandbox pod -echo $FILE_BROWSER_USERNAME -echo $FILE_BROWSER_PASSWORD -``` - -### Issue 7: Database Connection Failed - -**Symptom**: Sandbox can't connect to PostgreSQL - -**Cause**: Database not ready or wrong connection string - -**Solution**: Check database status and connection URL - -```bash -# Check database status -kubectl get cluster -n {namespace} - -# Check connection URL -echo $DATABASE_URL - -# Test connection -psql $DATABASE_URL -``` - -## Debugging Commands - -### Kubernetes Resources - -```bash -# Set kubeconfig -export KUBECONFIG=/path/to/kubeconfig - -# Check StatefulSets -kubectl get statefulsets -n {namespace} | grep {project-name} - -# Check pods -kubectl get pods -n {namespace} -l app={statefulset-name} - -# Pod logs -kubectl logs -n {namespace} {pod-name} - -# Pod logs (follow) -kubectl logs -f -n {namespace} {pod-name} - -# Check KubeBlocks database cluster -kubectl get cluster -n {namespace} | grep {project-name} - -# Get database credentials -kubectl get secret -n {namespace} {cluster-name}-conn-credential -o yaml - -# Check ingresses -kubectl get ingress -n {namespace} | grep {project-name} - -# Describe resource for events -kubectl describe statefulset -n {namespace} {statefulset-name} -``` - -### Database Queries - -```bash -# Open Prisma Studio -npx prisma studio - -# Direct PostgreSQL queries -psql $DATABASE_URL - -# Check locked resources -psql $DATABASE_URL -c "SELECT id, status, \"lockedUntil\" FROM \"Sandbox\" WHERE \"lockedUntil\" IS NOT NULL;" -``` - -### Application Logs - -```bash -# Main application logs -kubectl logs -n {namespace} {pod-name} - -# Filter by module -kubectl logs -n {namespace} {pod-name} | grep "lib/events/sandbox" - -# Filter by level -kubectl logs -n {namespace} {pod-name} | grep "ERROR" -``` - -## Error Messages - -### "User does not have KUBECONFIG configured" - -**Cause**: User hasn't uploaded kubeconfig - -**Solution**: -1. Check UserConfig table for KUBECONFIG key -2. User needs to configure kubeconfig via UI or API - -### "Project not found" - -**Cause**: Project doesn't exist or user doesn't have access - -**Solution**: -1. Check project ID -2. Check user ID matches project owner -3. Check namespace matches user's kubeconfig - -### "Cannot start project - invalid status transition" - -**Cause**: Project not in correct state for start - -**Solution**: -1. Check current project status -2. Only STOPPED projects can be started -3. Wait for current operation to complete - -### "Environment variables can only be updated when the project is running" - -**Cause**: Project not in RUNNING state - -**Solution**: -1. Check project status -2. Start project first -3. Wait for RUNNING status - -### "Failed to create sandbox" - -**Cause**: Various K8s errors - -**Solution**: -1. Check K8s events: `kubectl describe statefulset` -2. Check resource quotas -3. Check image availability -4. Check namespace exists - -## Performance Issues - -### Slow API Responses - -**Possible Causes**: -1. Database query performance -2. Missing indexes -3. N+1 query problem - -**Solutions**: -```typescript -// Use include for relations -await prisma.project.findMany({ - include: { sandboxes: true, databases: true } -}) - -// Check query performance - -## Incident References - -- GitHub import delay postmortem: - - [project-import-delay-postmortem.md](/Users/che/Documents/GitHub/fulling/docs/project-import-delay-postmortem.md) -const prisma = new PrismaClient({ - log: ['query', 'info', 'warn', 'error'], -}) -``` - -### Slow Reconciliation - -**Possible Causes**: -1. Too many resources to process -2. K8s API throttling -3. Lock contention - -**Solutions**: -1. Reduce batch size in reconciliation job -2. Increase reconciliation interval -3. Check K8s API server load - -### High Memory Usage - -**Possible Causes**: -1. Memory leaks in event listeners -2. Large objects in memory -3. Unclosed connections - -**Solutions**: -1. Check for memory leaks -2. Use streaming for large data -3. Close connections properly - -### Slow Sandbox Startup - -**Possible Causes**: -1. Large runtime image -2. Slow PVC provisioning -3. Resource constraints - -**Solutions**: -1. Use smaller base image -2. Check storage class performance -3. Increase resource limits - -## Related Documentation - -- [Architecture](./architecture.md) - Reconciliation pattern and event system -- [Development Guide](./development.md) - Local development and code patterns -- [Operations Manual](./operations.md) - Deployment and K8s operations diff --git a/docs/wiki/_meta/manifest.json b/docs/wiki/_meta/manifest.json deleted file mode 100644 index 79568dc..0000000 --- a/docs/wiki/_meta/manifest.json +++ /dev/null @@ -1,232 +0,0 @@ -{ - "generated_at": "2026-04-02T02:16:55Z", - "wiki_root": "docs/wiki", - "router_mode": "app", - "pages": [ - { - "path": "docs/wiki/index.md", - "sources": [ - "package.json", - "app", - "lib/platform/readme.md", - "prisma/schema.prisma" - ] - }, - { - "path": "docs/wiki/architecture.md", - "sources": [ - "app/layout.tsx", - "instrumentation.ts", - "lib/startup/index.ts", - "lib/platform/readme.md", - "lib/platform/control/readme.md", - "lib/platform/control/commands/readme.md", - "lib/actions", - "lib/repo", - "lib/jobs", - "lib/events" - ] - }, - { - "path": "docs/wiki/routing.md", - "sources": [ - "app", - "components/sidebar.tsx", - "components/sidebars/project-sidebar.tsx", - "components/layout/project-content-wrapper.tsx", - "app/(dashboard)/settings/_components/settings-sidebar.tsx" - ] - }, - { - "path": "docs/wiki/rendering-and-data-flow.md", - "sources": [ - "app/layout.tsx", - "app/(landing)/page.tsx", - "provider/providers.tsx", - "hooks/use-project.ts", - "hooks/use-environment-variables.ts", - "components/layout/project-content-wrapper.tsx", - "lib/fetch-client.ts", - "lib/data/project.ts" - ] - }, - { - "path": "docs/wiki/auth-and-state.md", - "sources": [ - "lib/auth.ts", - "lib/api-auth.ts", - "provider/sealos.tsx", - "lib/actions/sealos-auth.ts", - "app/(auth)/login/page.tsx" - ] - }, - { - "path": "docs/wiki/config-and-env.md", - "sources": [ - "next.config.ts", - "package.json", - "lib/env.ts", - "instrumentation.ts", - "lib/startup/index.ts", - "lib/const.ts" - ] - }, - { - "path": "docs/wiki/data-models.md", - "sources": [ - "prisma/schema.prisma", - "lib/repo/project.ts" - ] - }, - { - "path": "docs/wiki/integrations.md", - "sources": [ - "lib/k8s/k8s-service-helper.ts", - "lib/services/github-app.ts", - "lib/services/aiproxy.ts", - "lib/util/ttyd-context.ts", - "provider/sealos.tsx", - "lib/platform/integrations/README.md" - ] - }, - { - "path": "docs/wiki/background-jobs.md", - "sources": [ - "instrumentation.ts", - "lib/startup/index.ts", - "lib/jobs/sandbox/sandboxReconcile.ts", - "lib/jobs/database/databaseReconcile.ts", - "lib/jobs/project-task/projectTaskReconcile.ts", - "lib/events/sandbox/sandboxListener.ts", - "lib/events/database/databaseListener.ts" - ] - }, - { - "path": "docs/wiki/todo.md", - "sources": [ - "components/sidebar.tsx", - "app/(dashboard)/settings/_components/settings-sidebar.tsx", - "app/(dashboard)/projects/(list)/_components/project-actions-menu.tsx", - "app/(dashboard)/projects/[id]/exec-test/page.tsx", - "lib/actions/sandbox.ts", - "lib/platform/readme.md", - "app/api" - ] - }, - { - "path": "docs/wiki/api/index.md", - "sources": [ - "app/api", - "lib/api-auth.ts" - ] - }, - { - "path": "docs/wiki/api/auth-and-github.md", - "sources": [ - "app/api/auth/[...nextauth]/route.ts", - "app/api/github/app/callback/route.ts", - "app/api/github/app/webhook/route.ts", - "lib/auth.ts", - "lib/services/github-app.ts", - "app/github/app/callback/page.tsx" - ] - }, - { - "path": "docs/wiki/api/projects-and-runtime.md", - "sources": [ - "app/api/projects/[id]/route.ts", - "app/api/projects/[id]/start/route.ts", - "app/api/projects/[id]/stop/route.ts", - "app/api/projects/[id]/delete/route.ts", - "app/api/projects/[id]/environment/route.ts", - "app/api/projects/[id]/environment/[envId]/route.ts", - "app/api/sandbox/[id]/cwd/route.ts", - "app/api/sandbox/[id]/exec/route.ts", - "app/api/sandbox/[id]/app-status/route.ts", - "hooks/use-project.ts", - "hooks/use-project-operations.ts", - "hooks/use-environment-variables.ts", - "hooks/use-app-runner.ts", - "components/terminal/xterm-terminal.tsx" - ] - }, - { - "path": "docs/wiki/api/user-config.md", - "sources": [ - "app/api/user/config/route.ts", - "app/api/user/config/anthropic/route.ts", - "app/api/user/config/kc/route.ts", - "app/api/user/config/system-prompt/route.ts", - "components/dialog/settings-dialog.tsx", - "lib/k8s/k8s-service-helper.ts" - ] - }, - { - "path": "docs/wiki/features/auth-and-identity.md", - "sources": [ - "app/(auth)/login/page.tsx", - "app/(auth)/auth-error/page.tsx", - "app/github/app/callback/page.tsx", - "lib/auth.ts", - "provider/sealos.tsx", - "lib/actions/sealos-auth.ts" - ] - }, - { - "path": "docs/wiki/features/project-workspace.md", - "sources": [ - "app/(dashboard)/projects/(list)/page.tsx", - "app/(dashboard)/projects/[id]/layout.tsx", - "app/(dashboard)/projects/[id]/database/page.tsx", - "app/(dashboard)/projects/[id]/environment/page.tsx", - "app/(dashboard)/projects/[id]/secrets/page.tsx", - "app/(dashboard)/projects/[id]/auth/page.tsx", - "app/(dashboard)/projects/[id]/payment/page.tsx", - "components/layout/project-content-wrapper.tsx", - "hooks/use-project.ts", - "hooks/use-project-operations.ts", - "hooks/use-environment-variables.ts", - "lib/actions/project.ts", - "lib/actions/database.ts" - ] - }, - { - "path": "docs/wiki/features/github-and-import.md", - "sources": [ - "app/(dashboard)/projects/_components/import-github-dialog.tsx", - "app/(dashboard)/projects/[id]/github/page.tsx", - "app/github/app/callback/page.tsx", - "lib/actions/github.ts", - "lib/actions/project.ts", - "lib/services/github-app.ts", - "lib/services/repoService.ts", - "lib/platform/control/commands/project/create-project-from-github.ts", - "lib/jobs/project-task/executors/clone-repository.ts" - ] - }, - { - "path": "docs/wiki/features/user-settings.md", - "sources": [ - "app/(dashboard)/settings/page.tsx", - "app/(dashboard)/settings/integrations/page.tsx", - "app/(dashboard)/settings/_components/github-status-card.tsx", - "app/(dashboard)/settings/_components/settings-sidebar.tsx", - "components/dialog/settings-dialog.tsx", - "provider/sealos.tsx" - ] - }, - { - "path": "docs/wiki/features/global-skills.md", - "sources": [ - "app/(dashboard)/skills/page.tsx", - "app/(dashboard)/skills/_components/skills-library.tsx", - "lib/actions/skill.ts", - "lib/skills/catalog.ts", - "lib/platform/control/commands/skill/enable-global-skill.ts", - "lib/platform/control/commands/skill/uninstall-global-skill.ts", - "lib/jobs/project-task/executors/install-skill.ts", - "lib/jobs/project-task/executors/uninstall-skill.ts" - ] - } - ] -} diff --git a/docs/wiki/api/auth-and-github.md b/docs/wiki/api/auth-and-github.md deleted file mode 100644 index e2811f7..0000000 --- a/docs/wiki/api/auth-and-github.md +++ /dev/null @@ -1,138 +0,0 @@ -# Auth and GitHub API - -## Domain Summary - -This domain covers identity exchange, GitHub App installation binding, and GitHub webhook lifecycle updates. - -## Public Endpoints - -### `GET | POST /api/auth/[...nextauth]` - -Type: `public` - -Source: - -- `app/api/auth/[...nextauth]/route.ts` - route entry -- `lib/auth.ts` - provider and callback configuration - -Auth: - -- Owned by NextAuth -- Provider-specific auth happens inside the NextAuth handler - -Request: - -- Params: dynamic catch-all segment used by NextAuth -- Query: NextAuth-managed -- Body: provider-specific, including credentials or OAuth callback payloads -- Headers: browser session and CSRF cookies as required by NextAuth - -Response: - -- Success: NextAuth-managed redirects, session cookies, or JSON depending on the auth sub-route -- Failure: NextAuth error responses or redirect to `/auth-error` - -Side effects: - -- creates or updates `User` -- creates or updates `UserIdentity` -- stores JWT-backed session state -- sets cross-site-friendly auth cookies - -Main callers: - -- `app/(auth)/login/page.tsx` - credentials and GitHub sign-in UI -- `lib/actions/sealos-auth.ts` - Sealos server-side sign-in helper - -### `GET /api/github/app/callback` - -Type: `public` - -Source: - -- `app/api/github/app/callback/route.ts` - route entry -- `lib/services/github-app.ts` - OAuth exchange and installation lookup -- `lib/repo/github.ts` - installation persistence - -Auth: - -- Requires an authenticated Fulling session -- Verifies installation ownership unless OAuth exchange already established that link - -Request: - -- Params: none -- Query: - - `installation_id` required - - `setup_action` optional - - `code` optional -- Body: none -- Headers: session cookies - -Response: - -- Success: small HTML page that posts a success message to the opener and closes itself -- Failure: JSON error for missing auth or invalid install context, or failure HTML page - -Side effects: - -- may exchange GitHub OAuth code for user tokens -- upserts `GITHUB` `UserIdentity` -- creates or updates `GitHubAppInstallation` - -Main callers: - -- `app/github/app/callback/page.tsx` - popup completion page that forwards query params here -- GitHub App installation redirect flow - -### `POST /api/github/app/webhook` - -Type: `public` - -Source: - -- `app/api/github/app/webhook/route.ts` - route entry -- `lib/services/github-app.ts` - webhook signature verification -- `lib/repo/github.ts` - installation status updates - -Auth: - -- No session auth -- Requires valid `x-hub-signature-256` verification - -Request: - -- Params: none -- Query: none -- Body: GitHub webhook JSON payload -- Headers: - - `x-hub-signature-256` - - `x-github-event` - -Response: - -- Success: `{ "ok": true }` -- Failure: `401` for invalid signature, `500` for processing failures - -Side effects: - -- updates GitHub installation status for `deleted`, `suspend`, and `unsuspend` - -Main callers: - -- GitHub webhook delivery - -## Shared Dependencies - -- `lib/auth.ts` -- `lib/services/github-app.ts` -- `lib/repo/github.ts` -- Prisma models `UserIdentity` and `GitHubAppInstallation` - -## Main Callers - -- login page -- Sealos auth server action -- popup callback page -- GitHub App install popup flows from settings and import dialogs - diff --git a/docs/wiki/api/index.md b/docs/wiki/api/index.md deleted file mode 100644 index d959fb2..0000000 --- a/docs/wiki/api/index.md +++ /dev/null @@ -1,51 +0,0 @@ -# API Index - -## API Overview - -The repository exposes internal application APIs rather than a broad external developer platform. - -Main route families: - -- auth and GitHub lifecycle -- projects and runtime control -- user-scoped configuration - -All authenticated internal routes use App Router route handlers under `app/api/`. - -## Public Interfaces - -Public-facing route surfaces are limited to: - -- `GET | POST /api/auth/[...nextauth]` -- `GET /api/github/app/callback` -- `POST /api/github/app/webhook` - -Notes: - -- The auth route is public because NextAuth owns its exchange protocol. -- The GitHub callback is reachable from an external install flow, but still requires an authenticated Fulling session to bind the installation to a user. -- The webhook is public but guarded by GitHub signature verification. - -## Internal Interfaces - -Internal route surfaces cover: - -- project polling and lifecycle status changes -- project environment variable CRUD -- sandbox execution helpers -- user config storage and validation - -These routes are primarily consumed by in-repo hooks and dialogs rather than third-party clients. - -## Domain Index - -- [Auth and GitHub](./auth-and-github.md) -- [Projects and Runtime](./projects-and-runtime.md) -- [User Config](./user-config.md) - -## Notable Auth Gates - -- `withAuth` in `lib/api-auth.ts` is the standard API wrapper. -- Ownership checks are performed per project or sandbox. -- No API-wide middleware layer was found. - diff --git a/docs/wiki/api/projects-and-runtime.md b/docs/wiki/api/projects-and-runtime.md deleted file mode 100644 index 47cf5e7..0000000 --- a/docs/wiki/api/projects-and-runtime.md +++ /dev/null @@ -1,450 +0,0 @@ -# Projects and Runtime API - -## Domain Summary - -This domain covers live project reads, project lifecycle intent, project environment variables, and low-level sandbox helpers. - -Everything here is app-internal and owner-scoped. - -## Internal Endpoints - -### `GET /api/projects/[id]` - -Type: `internal` - -Source: - -- `app/api/projects/[id]/route.ts` - route entry - -Auth: - -- `withAuth` -- project row filtered by `userId` - -Request: - -- Params: `id` project ID -- Query: none -- Body: none -- Headers: session cookies - -Response: - -- Success: project with ordered sandboxes, databases, environments, and tasks -- Failure: `404` when the project is not owned by the user, `500` on read failure - -Side effects: - -- none - -Main callers: - -- `hooks/use-project.ts` -- `app/(dashboard)/projects/_components/import-github-dialog.tsx` - -### `POST /api/projects/[id]/start` - -Type: `internal` - -Source: - -- `app/api/projects/[id]/start/route.ts` -- `lib/repo/project.ts` -- `lib/util/action.ts` - -Auth: - -- `withAuth` -- project row filtered by `userId` - -Request: - -- Params: `id` project ID -- Query: none -- Body: none -- Headers: session cookies - -Response: - -- Success: message plus updated project status -- Failure: `404` when missing, `400` when current state does not allow start - -Side effects: - -- updates databases and sandboxes to `STARTING` -- updates project status to `STARTING` - -Main callers: - -- `hooks/use-project-operations.ts` - -### `POST /api/projects/[id]/stop` - -Type: `internal` - -Source: - -- `app/api/projects/[id]/stop/route.ts` -- `lib/repo/project.ts` -- `lib/util/action.ts` - -Auth: - -- `withAuth` -- project row filtered by `userId` - -Request: - -- Params: `id` project ID -- Query: none -- Body: none -- Headers: session cookies - -Response: - -- Success: message plus updated project status -- Failure: `404` when missing, `400` when current state does not allow stop - -Side effects: - -- updates databases and sandboxes to `STOPPING` -- updates project status to `STOPPING` - -Main callers: - -- `hooks/use-project-operations.ts` - -### `POST /api/projects/[id]/delete` - -Type: `internal` - -Source: - -- `app/api/projects/[id]/delete/route.ts` -- `lib/repo/project.ts` -- `lib/util/action.ts` - -Auth: - -- `withAuth` -- project row filtered by `userId` - -Request: - -- Params: `id` project ID -- Query: none -- Body: none -- Headers: session cookies - -Response: - -- Success: message plus updated project status -- Failure: `404` when missing, `400` when current state does not allow delete - -Side effects: - -- updates databases and sandboxes to `TERMINATING` -- eventually leads to hard deletion of runtime rows and the project row after reconcile - -Main callers: - -- `hooks/use-project-operations.ts` - -### `GET /api/projects/[id]/environment` - -Type: `internal` - -Source: - -- `app/api/projects/[id]/environment/route.ts` - -Auth: - -- `withAuth` -- `verifyProjectAccess` - -Request: - -- Params: `id` project ID -- Query: none -- Body: none -- Headers: session cookies - -Response: - -- Success: grouped environment variables by category -- Failure: `500` on lookup failure - -Side effects: - -- none - -Main callers: - -- `hooks/use-environment-variables.ts` - -### `POST /api/projects/[id]/environment` - -Type: `internal` - -Source: - -- `app/api/projects/[id]/environment/route.ts` - -Auth: - -- `withAuth` -- `verifyProjectAccess` - -Request: - -- Params: `id` project ID -- Query: none -- Body: - - single variable `{ key, value, category?, isSecret? }`, or - - batch `{ variables: [...] }` -- Headers: session cookies - -Response: - -- Success: created variable or `{ success: true, count }` -- Failure: - - `400` for invalid body or project not fully running - - `404` when project is missing - - `500` on write failure - -Side effects: - -- creates or replaces environment rows -- marks running sandboxes as `UPDATING` - -Main callers: - -- `hooks/use-environment-variables.ts` - -### `PUT /api/projects/[id]/environment/[envId]` - -Type: `internal` - -Source: - -- `app/api/projects/[id]/environment/[envId]/route.ts` - -Auth: - -- `withAuth` -- `verifyProjectAccess` - -Request: - -- Params: - - `id` project ID - - `envId` environment row ID -- Query: none -- Body: `{ value }` -- Headers: session cookies - -Response: - -- Success: updated environment row -- Failure: - - `400` for missing value or non-running project - - `404` when the env row is not in that project - - `500` on write failure - -Side effects: - -- updates one env row -- marks running sandboxes as `UPDATING` - -Main callers: - -- `hooks/use-environment-variables.ts` - -### `DELETE /api/projects/[id]/environment/[envId]` - -Type: `internal` - -Source: - -- `app/api/projects/[id]/environment/[envId]/route.ts` - -Auth: - -- `withAuth` -- `verifyProjectAccess` - -Request: - -- Params: - - `id` project ID - - `envId` environment row ID -- Query: none -- Body: none -- Headers: session cookies - -Response: - -- Success: `{ success: true }` -- Failure: - - `400` when the project is not fully running - - `404` when the env row is not in that project - - `500` on delete failure - -Side effects: - -- deletes one env row -- marks running sandboxes as `UPDATING` - -Main callers: - -- `hooks/use-environment-variables.ts` - -### `GET /api/sandbox/[id]/cwd` - -Type: `internal` - -Source: - -- `app/api/sandbox/[id]/cwd/route.ts` -- `lib/k8s/k8s-service-helper.ts` - -Auth: - -- `withAuth` -- sandbox ownership resolved from the parent project - -Request: - -- Params: `id` sandbox ID -- Query: `sessionId` required -- Body: none -- Headers: session cookies - -Response: - -- Success: `{ cwd, homeDir, isInHome }` -- Failure: `400` for missing query param, `500` for runtime errors - -Side effects: - -- none - -Main callers: - -- `components/terminal/xterm-terminal.tsx` - -### `POST /api/sandbox/[id]/exec` - -Type: `internal` - -Source: - -- `app/api/sandbox/[id]/exec/route.ts` -- `lib/k8s/k8s-service-helper.ts` - -Auth: - -- `withAuth` -- sandbox ownership resolved from the parent project - -Request: - -- Params: `id` sandbox ID -- Query: none -- Body: `{ command, workdir? }` -- Headers: session cookies - -Response: - -- Success: `{ success: true, pid }` -- Failure: `400` for missing command, `500` for execution failure - -Side effects: - -- starts a background process in the sandbox - -Main callers: - -- `hooks/use-app-runner.ts` - -### `GET /api/sandbox/[id]/app-status` - -Type: `internal` - -Source: - -- `app/api/sandbox/[id]/app-status/route.ts` - -Auth: - -- `withAuth` -- sandbox ownership resolved from the parent project - -Request: - -- Params: `id` sandbox ID -- Query: none -- Body: none -- Headers: session cookies - -Response: - -- Success: `{ running: boolean }` -- Failure: returns `{ running: false }` on handler error - -Side effects: - -- none - -Main callers: - -- no direct in-repo caller was found - -### `DELETE /api/sandbox/[id]/app-status` - -Type: `internal` - -Source: - -- `app/api/sandbox/[id]/app-status/route.ts` - -Auth: - -- `withAuth` -- sandbox ownership resolved from the parent project - -Request: - -- Params: `id` sandbox ID -- Query: none -- Body: none -- Headers: session cookies - -Response: - -- Success: `{ success: true }` or `{ success: false, error }` -- Failure: `500` when the stop operation itself throws - -Side effects: - -- kills the process listening on sandbox port `3000` - -Main callers: - -- no direct in-repo caller was found - -## Shared Dependencies - -- `lib/api-auth.ts` -- `lib/repo/project.ts` -- `lib/k8s/k8s-service-helper.ts` -- `lib/util/action.ts` - -## Main Callers - -- `hooks/use-project.ts` -- `hooks/use-project-operations.ts` -- `hooks/use-environment-variables.ts` -- terminal components and sandbox execution hooks - diff --git a/docs/wiki/api/user-config.md b/docs/wiki/api/user-config.md deleted file mode 100644 index b2ec8fe..0000000 --- a/docs/wiki/api/user-config.md +++ /dev/null @@ -1,283 +0,0 @@ -# User Config API - -## Domain Summary - -This domain stores user-scoped configuration rather than project-scoped runtime state. It backs the settings dialog and parts of the integrations page. - -## Internal Endpoints - -### `GET /api/user/config` - -Type: `internal` - -Source: - -- `app/api/user/config/route.ts` - -Auth: - -- `withAuth` - -Request: - -- Params: none -- Query: `keys=KEY1,KEY2` required -- Body: none -- Headers: session cookies - -Response: - -- Success: `{ configs: { KEY: value } }` -- Failure: `400` when keys are missing, `500` on lookup failure - -Side effects: - -- none - -Main callers: - -- generic settings fetch usage - -### `POST /api/user/config` - -Type: `internal` - -Source: - -- `app/api/user/config/route.ts` - -Auth: - -- `withAuth` - -Request: - -- Params: none -- Query: none -- Body: `{ configs: [{ key, value, category?, isSecret? }] }` -- Headers: session cookies - -Response: - -- Success: `{ success: true, configs: [...] }` -- Failure: `400` for invalid bodies, `500` on write failure - -Side effects: - -- upserts one or more `UserConfig` rows - -Main callers: - -- generic settings save usage - -### `GET /api/user/config/anthropic` - -Type: `internal` - -Source: - -- `app/api/user/config/anthropic/route.ts` - -Auth: - -- `withAuth` - -Request: - -- Params: none -- Query: none -- Body: none -- Headers: session cookies - -Response: - -- Success: `{ apiKey, apiBaseUrl, model, smallFastModel }` -- Failure: `500` on lookup failure - -Side effects: - -- none - -Main callers: - -- `components/dialog/settings-dialog.tsx` - -### `POST /api/user/config/anthropic` - -Type: `internal` - -Source: - -- `app/api/user/config/anthropic/route.ts` - -Auth: - -- `withAuth` - -Request: - -- Params: none -- Query: none -- Body: - - `apiBaseUrl` required - - `apiKey` required - - `model` optional - - `smallFastModel` optional -- Headers: session cookies - -Response: - -- Success: `{ success: true, message }` -- Failure: `400` for missing or invalid values, `500` on write failure - -Side effects: - -- upserts or deletes Anthropic-related `UserConfig` rows in a transaction - -Main callers: - -- `components/dialog/settings-dialog.tsx` - -### `GET /api/user/config/kc` - -Type: `internal` - -Source: - -- `app/api/user/config/kc/route.ts` - -Auth: - -- `withAuth` - -Request: - -- Params: none -- Query: none -- Body: none -- Headers: session cookies - -Response: - -- Success: `{ kubeconfig, namespace }` -- Failure: `404` when kubeconfig is absent, `500` on lookup failure - -Side effects: - -- none - -Main callers: - -- `components/dialog/settings-dialog.tsx` - -### `POST /api/user/config/kc` - -Type: `internal` - -Source: - -- `app/api/user/config/kc/route.ts` -- `lib/k8s/k8s-service-helper.ts` -- `lib/k8s/kubernetes-utils.ts` - -Auth: - -- `withAuth` - -Request: - -- Params: none -- Query: none -- Body: `{ kubeconfig }` -- Headers: session cookies - -Response: - -- Success: `{ success: true, namespace, message }` -- Failure: `400` for missing or invalid kubeconfig, `500` on write failure - -Side effects: - -- validates kubeconfig -- upserts `KUBECONFIG` -- clears the cached per-user Kubernetes service instance - -Main callers: - -- `components/dialog/settings-dialog.tsx` - -### `GET /api/user/config/system-prompt` - -Type: `internal` - -Source: - -- `app/api/user/config/system-prompt/route.ts` - -Auth: - -- `withAuth` - -Request: - -- Params: none -- Query: none -- Body: none -- Headers: session cookies - -Response: - -- Success: `{ systemPrompt }` -- Failure: `500` on lookup failure - -Side effects: - -- none - -Main callers: - -- `components/dialog/settings-dialog.tsx` - -### `POST /api/user/config/system-prompt` - -Type: `internal` - -Source: - -- `app/api/user/config/system-prompt/route.ts` - -Auth: - -- `withAuth` - -Request: - -- Params: none -- Query: none -- Body: `{ systemPrompt }` -- Headers: session cookies - -Response: - -- Success: `{ success: true, message }` -- Failure: `400` for missing prompt, `500` on write failure - -Side effects: - -- upserts `SYSTEM_PROMPT` in `UserConfig` - -Main callers: - -- `components/dialog/settings-dialog.tsx` - -## Shared Dependencies - -- `lib/api-auth.ts` -- Prisma `UserConfig` -- `components/dialog/settings-dialog.tsx` - -## Main Callers - -- settings dialog -- user settings flows - diff --git a/docs/wiki/architecture.md b/docs/wiki/architecture.md deleted file mode 100644 index 4f895ba..0000000 --- a/docs/wiki/architecture.md +++ /dev/null @@ -1,136 +0,0 @@ -# Architecture - -## Overview - -Fulling splits responsibility between a Next.js interaction layer and a database-backed control plane. - -At a high level: - -1. App Router pages and client components collect user intent. -2. Internal APIs or server actions persist that intent in PostgreSQL through Prisma. -3. Reconcile jobs poll for records in transition states. -4. Event listeners call user-scoped Kubernetes or GitHub integrations. -5. Repository records are updated with the latest status, URLs, credentials, or task results. - -## Repository Shape - -- `app/` - - App Router pages, route handlers, layouts, and route-local components. -- `components/`, `hooks/`, `provider/` - - Client UI primitives, React Query hooks, session/theme/Sealos providers. -- `lib/actions/` - - Server actions used by client components for project, database, GitHub, sandbox, and skill operations. -- `lib/data/` - - Server-side read helpers for Server Components. -- `lib/api-auth.ts` - - Shared API authorization and ownership checks. -- `lib/repo/` - - Persistence helpers, lock acquisition, status reconciliation, and ownership-aware updates. -- `lib/events/` and `lib/jobs/` - - In-process event buses and cron-driven reconciliation. -- `lib/platform/` - - Newer control-plane layers: control, persistence, integrations, executors, and orchestrator scaffolding. -- `lib/k8s/` and `lib/services/` - - Provider-facing runtime logic for Kubernetes, GitHub App, ttyd, and Anthropic proxy support. - -## Runtime Boundaries - -### UI Layer - -The UI is App Router only. Server components gate authenticated routes and fetch initial data. Client components then handle interactive state, polling, forms, dialogs, and terminal UX. - -Key modules: - -- `app/layout.tsx` - root providers and global metadata -- `app/(dashboard)/projects/[id]/layout.tsx` - project ownership gate and persistent workspace shell -- `components/layout/project-content-wrapper.tsx` - persistent terminal panel plus route-driven content panel -- `components/dialog/settings-dialog.tsx` - user-scoped settings surface - -### Server Layer - -The server layer is split between internal API routes and server actions. - -- Internal API routes are used for authenticated app behavior such as project polling, environment variable writes, sandbox command execution, and user config persistence. -- Server actions are used where the UI wants a direct write-side entrypoint without modeling a public route shape. - -Key modules: - -- `app/api/**/route.ts` -- `lib/actions/project.ts` -- `lib/actions/database.ts` -- `lib/actions/skill.ts` -- `lib/actions/github.ts` - -### Control Plane - -The control plane is responsible for durable intent, not immediate infrastructure effects. - -- `lib/platform/control/commands/` accepts validated intent. -- `lib/platform/persistence/` creates the rows and queued task records needed to represent that intent. -- `lib/repo/` handles lower-level updates, row locking, and status aggregation. - -Notable examples: - -- `createProjectCommand` creates a project, sandbox metadata, and initial skill install tasks. -- `createProjectFromGitHubCommand` verifies repository access, creates the project, and queues a clone task. -- `createDatabaseCommand` creates the database control-plane row but does not create the database immediately. - -### Reconcile and Event Layer - -Background jobs poll for state transitions and emit typed events. - -- `lib/jobs/sandbox/sandboxReconcile.ts` -- `lib/jobs/database/databaseReconcile.ts` -- `lib/jobs/project-task/projectTaskReconcile.ts` -- `lib/events/sandbox/sandboxListener.ts` -- `lib/events/database/databaseListener.ts` - -This is the clearest expression of the repository's asynchronous reconciliation pattern. - -## Core Request Paths - -### Blank Project Creation - -1. The create project dialog calls `createProject` in `lib/actions/project.ts`. -2. `createProjectCommand` validates the name, resolves the user's default namespace, and creates project and sandbox rows. -3. The sandbox starts in a transition state. -4. The sandbox reconcile job locks that row and emits sandbox lifecycle events. -5. The sandbox listener calls the user-scoped Kubernetes service and updates URLs and status. - -### GitHub Import - -1. The import dialog reads GitHub App installations and repositories through server actions. -2. `createProjectFromGitHubCommand` creates project and sandbox rows, then queues `CLONE_REPOSITORY`. -3. The task reconcile job waits for the sandbox to reach `RUNNING`. -4. The clone executor uses a GitHub App installation token and ttyd command execution to clone the repository inside the sandbox. - -### Environment Variable Update - -1. Project config pages use `useEnvironmentVariables` and `useBatchUpdateEnvironmentVariables`. -2. Internal API routes persist environment rows only when the project is already running. -3. Successful writes mark sandboxes as `UPDATING`. -4. Sandbox reconcile then reloads project and Anthropic env vars and pushes them into Kubernetes. - -## Shared Libraries and Cross-Cutting Concerns - -- Prisma is the only persistence layer in active use. -- `react` cache is used in `lib/data/` to deduplicate request-scoped reads. -- React Query is the main client cache for live project state. -- `lib/fetch-client.ts` wraps browser fetch with timeout and automatic `401` redirect handling. -- `instrumentation.ts` and `lib/startup/index.ts` ensure listeners and jobs are only initialized once per server process. - -## External Dependencies - -- PostgreSQL via Prisma -- Kubernetes through user-provided kubeconfig -- Sealos desktop SDK for iframe-aware bootstrap -- GitHub App OAuth, installation APIs, and webhook verification -- ttyd for remote shell execution inside sandboxes -- Anthropic-compatible proxy settings stored in user config and projected into sandboxes - -## Constraints - -- The control plane is mid-transition: `lib/platform/` introduces a cleaner layered structure, but legacy `lib/repo/`, `lib/services/`, and `lib/actions/` modules are still heavily used. -- The schema allows multiple sandboxes and databases per project, but the current UI usually treats the first sandbox and first database as the primary runtime surfaces. -- Some navigation targets exist in sidebars without implemented routes; see [Routing](./routing.md). - diff --git a/docs/wiki/auth-and-state.md b/docs/wiki/auth-and-state.md deleted file mode 100644 index 38eb32f..0000000 --- a/docs/wiki/auth-and-state.md +++ /dev/null @@ -1,87 +0,0 @@ -# Auth and State - -## Auth Providers - -Authentication is configured centrally in `lib/auth.ts` through NextAuth v5. - -Provider availability is environment-driven: - -- password credentials -- Sealos credentials -- GitHub App OAuth - -### Password Flow - -The credentials provider doubles as sign-in and registration. - -- if the password identity exists, the password hash is verified -- if it does not exist, the user is created automatically with a new password hash - -### Sealos Flow - -Sealos auth is exposed through the `sealos` credentials provider. - -It: - -- validates the JWT -- extracts the Sealos user ID -- stores kubeconfig on the matching `UserIdentity` -- updates `UserConfig.KUBECONFIG` -- can bootstrap Anthropic proxy credentials for the user - -### GitHub Flow - -GitHub App OAuth uses a custom provider wrapper and a `signIn` callback that: - -- creates or updates a `GITHUB` `UserIdentity` -- persists access and refresh tokens -- maps the GitHub identity to a Fulling user - -## Session and Cookies - -- session strategy: `jwt` -- session payload is minimal and stores `user.id` and `user.name` -- cookies are configured with `sameSite: 'none'` and `secure: true` - -The cookie configuration is intentionally cross-site friendly for Sealos iframe embedding. - -## Authorization Checks - -The repository does not rely on middleware for authorization. - -Instead, checks happen close to the resource: - -- Server Components call `auth()` and redirect unauthenticated users -- API routes wrap handlers with `withAuth` -- project ownership checks use helpers such as `verifyProjectAccess` -- sandbox ownership checks resolve through the parent project relationship - -This produces an owner-scoped model rather than a role-based permission model. - -## Shared Client State - -The main shared state containers are: - -- `SessionProvider` -- `QueryClientProvider` -- `SealosProvider` -- `ThemeProvider` - -`SealosProvider` detects whether the app is running inside a Sealos iframe and, when applicable, loads the Sealos session and kubeconfig. - -## Form and Mutation State - -The UI mostly uses local React state for forms and tabs. Network state is layered on top through: - -- `useTransition` -- React Query mutations -- `toast` notifications - -This keeps state localized instead of centralizing all writes in a global store. - -## Constraints - -- Provider availability depends on environment flags, so deployment configuration can materially change the visible auth surface. -- GitHub App callback handling is partly public-facing but still expects an authenticated Fulling session to bind the installation to the correct user. -- The repository currently models ownership and identity, not granular roles or team permissions. - diff --git a/docs/wiki/background-jobs.md b/docs/wiki/background-jobs.md deleted file mode 100644 index d6de4b7..0000000 --- a/docs/wiki/background-jobs.md +++ /dev/null @@ -1,130 +0,0 @@ -# Background Jobs - -## Overview - -Fulling uses cron-driven reconcile loops instead of blocking user requests on infrastructure work. - -Startup wiring happens in: - -- `instrumentation.ts` -- `lib/startup/index.ts` - -On startup the app: - -1. registers sandbox and database event listeners -2. starts reconcile jobs for sandboxes, databases, and project tasks - -## Sandbox Reconcile - -Source: `lib/jobs/sandbox/sandboxReconcile.ts` - -Default behavior: - -- interval: every 7 seconds -- batch size: 10 rows -- lock window: 5 seconds by default, with jitter - -Eligible statuses: - -- `CREATING` -- `STARTING` -- `STOPPING` -- `TERMINATING` -- `UPDATING` - -The job atomically locks rows with `FOR UPDATE SKIP LOCKED`, then emits sandbox lifecycle events. - -## Database Reconcile - -Source: `lib/jobs/database/databaseReconcile.ts` - -Default behavior: - -- interval: every 11 seconds -- batch size: 10 rows -- lock window: 5 seconds by default, with jitter - -Eligible statuses: - -- `CREATING` -- `STARTING` -- `STOPPING` -- `TERMINATING` - -The database listener performs Kubernetes cluster operations and backfills connection credentials when the database becomes ready. - -## Project Task Reconcile - -Source: `lib/jobs/project-task/projectTaskReconcile.ts` - -Default behavior: - -- interval: every 13 seconds -- batch size: 10 rows -- reconcile lock window: 5 seconds by default -- execution lock window: 300 seconds by default - -Task orchestration logic includes: - -- prerequisite checks -- state transitions into `WAITING_FOR_PREREQUISITES` -- attempt counting and retry behavior -- special handling for superseded skill installs and clone prerequisites - -## Event Listener Behavior - -### Sandbox Events - -`lib/events/sandbox/sandboxListener.ts` handles: - -- create -- start -- stop -- delete -- update - -It also: - -- merges project env and Anthropic env before sandbox create or update -- updates sandbox URLs after creation -- triggers runnable project tasks when a sandbox reaches `RUNNING` - -### Database Events - -`lib/events/database/databaseListener.ts` handles: - -- create -- start -- stop -- delete - -It also: - -- fetches generated credentials once the cluster is ready -- updates project aggregate status after each transition - -## Task Executors - -Current executors under `lib/jobs/project-task/executors/` include: - -- clone repository -- install skill -- uninstall skill - -These executors use ttyd command execution inside the sandbox rather than direct Kubernetes file APIs. - -## Why This Matters - -This job model explains several user-visible behaviors: - -- project start and stop requests return before Kubernetes work finishes -- imported projects can exist before the repository has actually been cloned -- environment variable writes set the runtime to `UPDATING` and settle later -- global skill changes fan out across projects asynchronously - -## Constraints - -- All jobs run in-process inside the Next.js server process, so initialization correctness matters. -- The model assumes eventual convergence rather than immediate completion. -- Locks and polling avoid thundering herd behavior, but the user experience still depends on repeated status refreshes. - diff --git a/docs/wiki/config-and-env.md b/docs/wiki/config-and-env.md deleted file mode 100644 index d22679f..0000000 --- a/docs/wiki/config-and-env.md +++ /dev/null @@ -1,105 +0,0 @@ -# Config and Env - -## Configuration Files - -### `next.config.ts` - -The Next.js runtime is configured with: - -- `reactStrictMode: true` -- `output: 'standalone'` -- image allowlist for `avatars.githubusercontent.com` -- `serverExternalPackages: ['pino']` - -### `instrumentation.ts` - -The instrumentation hook initializes the application on server startup and calls `initializeApp()` from `lib/startup/index.ts`. - -That startup path registers listeners and starts background jobs once per process. - -### `package.json` - -Runtime assumptions: - -- Next.js 16 -- React 19 -- Node `>=22.12.0` -- pnpm `10.20.0` - -## Environment Variables - -### Platform-Level Variables - -Validated in `lib/env.ts`: - -- `DATABASE_URL` -- `RUNTIME_IMAGE` -- `ENABLE_PASSWORD_AUTH` -- `ENABLE_GITHUB_AUTH` -- `ENABLE_SEALOS_AUTH` -- `GITHUB_CLIENT_ID` -- `GITHUB_CLIENT_SECRET` -- `GITHUB_APP_ID` -- `GITHUB_APP_PRIVATE_KEY` -- `GITHUB_APP_WEBHOOK_SECRET` -- `GITHUB_APP_CLIENT_ID` -- `GITHUB_APP_CLIENT_SECRET` -- `ANTHROPIC_BASE_URL` -- `AIPROXY_ENDPOINT` -- `ANTHROPIC_MODEL` -- `ANTHROPIC_SMALL_FAST_MODEL` - -Client-exposed variables: - -- `NEXT_PUBLIC_GITHUB_APP_ID` -- `NEXT_PUBLIC_GITHUB_APP_NAME` - -### User-Scoped Runtime Config - -Stored in `UserConfig` and managed through settings APIs: - -- `KUBECONFIG` -- `ANTHROPIC_API_KEY` -- `ANTHROPIC_API` -- `ANTHROPIC_MODEL` -- `ANTHROPIC_SMALL_FAST_MODEL` -- `SYSTEM_PROMPT` - -Kubeconfig writes also clear the cached Kubernetes service instance for that user. - -### Project-Scoped Runtime Config - -Stored in the `Environment` table and managed per project. - -Known categories from `EnvironmentCategory`: - -- `auth` -- `payment` -- `ttyd` -- `file_browser` -- `general` -- `secret` - -Project configuration pages currently surface `auth`, `payment`, `general`, and `secret`. - -## Build-Time and Runtime Assumptions - -- The dev server and production server run on `0.0.0.0:3000`. -- Secure cookie behavior changes with `NODE_ENV`. -- Startup expects a Node.js runtime and does not initialize jobs in non-node runtimes. -- Reconcile behavior is interval-based and controlled by environment variables for lock duration and batch size. - -## Runtime Integrations - -Environment configuration directly influences: - -- auth provider availability -- GitHub App identity and webhook handling -- Kubernetes namespace and service access -- sandbox runtime image and projected env vars -- Anthropic-compatible proxy configuration inside sandboxes - -## Constraints - -- The repository includes `.env.template` and `yaml/.env.template`, but `lib/env.ts` remains the authoritative schema for validated platform env requirements. -- User config and project env are separate concerns. Global settings such as kubeconfig and Anthropic credentials do not live in project env rows. diff --git a/docs/wiki/data-models.md b/docs/wiki/data-models.md deleted file mode 100644 index d3137e6..0000000 --- a/docs/wiki/data-models.md +++ /dev/null @@ -1,171 +0,0 @@ -# Data Models - -## Overview - -Prisma models in `prisma/schema.prisma` describe both user identity and control-plane state. The main design split is: - -- user-scoped identity and configuration -- project-scoped runtime resources -- queued asynchronous task state - -## Identity and User Config - -### `User` - -Top-level owner for: - -- identities -- projects -- user config -- global skills -- GitHub App installations - -### `UserIdentity` - -Represents external auth identities and sensitive metadata. - -Current providers: - -- `PASSWORD` -- `GITHUB` -- `SEALOS` - -Metadata stores provider-specific details such as password hashes, OAuth tokens, or Sealos kubeconfig. - -### `UserConfig` - -Stores user-scoped settings such as: - -- kubeconfig -- Anthropic proxy values -- system prompt - -The model includes `category` and `isSecret` so the UI can group and mask values. - -## GitHub Integration - -### `GitHubAppInstallation` - -Tracks GitHub App installation ownership and status. - -Important fields: - -- `installationId` -- account metadata -- repository selection mode -- permissions and subscribed events -- lifecycle status: `ACTIVE`, `SUSPENDED`, `DELETED` - -Projects can reference a selected installation. - -## Project and Runtime Resources - -### `Project` - -The top-level application object. - -Important fields: - -- display metadata -- legacy `githubRepo` -- newer GitHub App-backed repository metadata -- aggregated `status` - -Relations: - -- environments -- databases -- sandboxes -- project tasks - -### `Environment` - -Per-project key/value configuration rows. These are not independent runtime resources and have no lifecycle status. - -### `Sandbox` - -Represents the runtime workspace. - -Important fields: - -- Kubernetes namespace and sandbox name -- public app URL -- ttyd URL -- file browser URL -- runtime image and resource requests -- resource lifecycle status -- optimistic lock window - -### `Database` - -Represents the optional PostgreSQL cluster for a project. - -Important fields: - -- Kubernetes namespace and database name -- connection credentials and URL -- resource lifecycle status -- optimistic lock window - -## Global Skills and Async Tasks - -### `UserSkill` - -Represents globally enabled skills for a user. - -The current UI exposes a catalog-backed global desired state rather than project-local toggles. - -### `ProjectTask` - -Represents asynchronous work such as: - -- `CLONE_REPOSITORY` -- `INSTALL_SKILL` -- `UNINSTALL_SKILL` -- `DEPLOY_PROJECT` - -Important fields: - -- `status` -- `triggerSource` -- `payload` -- `result` -- `attemptCount` -- `maxAttempts` -- lock and timing fields - -## Status Models - -### Resource Status - -Used by sandboxes and databases: - -- `CREATING` -- `STARTING` -- `RUNNING` -- `STOPPING` -- `STOPPED` -- `TERMINATING` -- `TERMINATED` -- `ERROR` -- `UPDATING` - -### Project Status - -Derived from child resource states rather than independently authored. - -This lets project status reflect mixed runtime conditions such as: - -- creating -- starting -- stopping -- terminating -- error -- partial - -## Important Semantics - -- Projects can be deleted automatically when all child runtime resources are gone and aggregate to `TERMINATED`. -- The schema allows more than one sandbox or database per project, but current UI flows usually operate on the first sandbox and the first database. -- Task execution is resilient rather than strictly synchronous; retries and prerequisite waiting are first-class concerns in the model. - diff --git a/docs/wiki/features/auth-and-identity.md b/docs/wiki/features/auth-and-identity.md deleted file mode 100644 index aacd81e..0000000 --- a/docs/wiki/features/auth-and-identity.md +++ /dev/null @@ -1,37 +0,0 @@ -# Auth and Identity - -## Purpose - -This domain gets a user into the product and binds external identities to a stable Fulling user record. - -## User-Facing Surfaces - -- `/login` - credentials sign-in plus GitHub sign-in -- `/auth-error` - auth failure surface -- `/github/app/callback` - popup completion view for GitHub App installation - -## Main Flows - -- Credentials sign-in doubles as auto-registration when the username does not exist yet. -- GitHub sign-in creates or refreshes a `GITHUB` identity and maps it onto a Fulling user. -- Sealos auth can bootstrap kubeconfig and AI proxy settings for users coming from a Sealos environment. - -## Supporting APIs - -- `GET | POST /api/auth/[...nextauth]` - NextAuth handler -- `GET /api/github/app/callback` - bind GitHub App installation to the current user - -## Key Modules - -- `app/(auth)/login/page.tsx` - login UI -- `lib/auth.ts` - auth providers, callbacks, and cookie/session policy -- `provider/sealos.tsx` - Sealos environment detection -- `lib/actions/sealos-auth.ts` - server-side Sealos sign-in helper -- `app/github/app/callback/page.tsx` - popup completion flow - -## Constraints - -- Available auth providers depend on environment flags. -- There is no role or team permission model yet; access is owner-scoped. -- GitHub App install completion is not enough by itself; the installation must still be bound to the authenticated Fulling user. - diff --git a/docs/wiki/features/github-and-import.md b/docs/wiki/features/github-and-import.md deleted file mode 100644 index 881085e..0000000 --- a/docs/wiki/features/github-and-import.md +++ /dev/null @@ -1,42 +0,0 @@ -# GitHub and Import - -## Purpose - -This domain connects Fulling projects to GitHub for repository import, installation tracking, and push-back to GitHub. - -## User-Facing Surfaces - -- project import dialog from `/projects` -- `/github/app/callback` - GitHub App install completion page -- `/projects/[id]/github` - repository initialize and push surface -- `/settings/integrations` - GitHub installation status summary - -## Main Flows - -- Install the GitHub App through a popup flow and persist the resulting installation. -- Select a repository from the installation and create a project in import mode. -- Wait for the sandbox to reach `RUNNING`, then clone the selected repository into the sandbox through a queued task. -- Initialize a new Git repository for a blank project and push changes back to GitHub from the project GitHub page. - -## Supporting APIs - -- `GET /api/github/app/callback` - installation binding -- `POST /api/github/app/webhook` - installation lifecycle updates -- `GET /api/projects/[id]` - import progress polling through project tasks - -## Key Modules - -- `app/(dashboard)/projects/_components/import-github-dialog.tsx` - repository picker and import progress poller -- `lib/actions/github.ts` - installation and repository reads -- `lib/actions/project.ts` - import action entrypoint -- `lib/platform/control/commands/project/create-project-from-github.ts` - import intent creation -- `lib/jobs/project-task/executors/clone-repository.ts` - in-sandbox clone execution -- `lib/services/github-app.ts` - GitHub App and OAuth service layer -- `lib/services/repoService.ts` - repo initialization and push helper for already-created projects - -## Constraints - -- Organization GitHub App installations are explicitly rejected today. -- Imported repositories are cloned into `/home/fulling/next/import/-`. -- The GitHub page for existing projects uses ttyd command execution and a `claude -p` commit command inside the sandbox when initializing and committing repositories. - diff --git a/docs/wiki/features/global-skills.md b/docs/wiki/features/global-skills.md deleted file mode 100644 index d68f993..0000000 --- a/docs/wiki/features/global-skills.md +++ /dev/null @@ -1,40 +0,0 @@ -# Global Skills - -## Purpose - -This domain defines shared skill desired state for a user and fans that state out across projects through asynchronous tasks. - -## User-Facing Surfaces - -- `/skills` - global skill library - -## Main Flows - -- Enable a global skill and create `INSTALL_SKILL` tasks for existing projects. -- Inherit currently enabled skills when new projects are created. -- Uninstall a global skill, cancel stale pending installs, and create `UNINSTALL_SKILL` tasks where removal is still needed. -- Execute install and uninstall commands inside project sandboxes after prerequisite checks pass. - -## Supporting APIs - -This domain does not currently expose a dedicated REST route. The UI uses server actions: - -- `enableGlobalSkill` -- `uninstallGlobalSkill` - -## Key Modules - -- `app/(dashboard)/skills/_components/skills-library.tsx` - skills UI -- `lib/actions/skill.ts` - server action entrypoints -- `lib/skills/catalog.ts` - source of available skills -- `lib/platform/control/commands/skill/enable-global-skill.ts` -- `lib/platform/control/commands/skill/uninstall-global-skill.ts` -- `lib/jobs/project-task/executors/install-skill.ts` -- `lib/jobs/project-task/executors/uninstall-skill.ts` - -## Constraints - -- The current catalog is intentionally small; only `frontend-design` is present in code today. -- Skill rollout depends on sandbox availability and, for imported projects, clone task completion. -- Uninstall does not auto-start stopped sandboxes just to remove a skill. - diff --git a/docs/wiki/features/project-workspace.md b/docs/wiki/features/project-workspace.md deleted file mode 100644 index 09e924d..0000000 --- a/docs/wiki/features/project-workspace.md +++ /dev/null @@ -1,52 +0,0 @@ -# Project Workspace - -## Purpose - -This domain is the day-to-day operating surface for a project: runtime status, terminal access, optional database, and project-scoped configuration. - -## User-Facing Surfaces - -- `/projects` - project list and creation entry -- `/projects/[id]/terminal` - primary runtime view -- `/projects/[id]/database` - optional PostgreSQL surface -- `/projects/[id]/environment` - general env vars -- `/projects/[id]/secrets` - secret env vars -- `/projects/[id]/auth` - auth-related env templates -- `/projects/[id]/payment` - payment-related env templates - -## Main Flows - -- Create a blank project from the dashboard dialog. -- Open the workspace shell, where the terminal remains mounted across route changes. -- Start, stop, or delete the project by updating resource statuses and waiting for reconcile. -- Add a database later if the project does not already have one. -- Edit environment variables only while the project runtime is already running. - -## Supporting APIs - -- `GET /api/projects/[id]` - live project state -- `POST /api/projects/[id]/start` - request runtime start -- `POST /api/projects/[id]/stop` - request runtime stop -- `POST /api/projects/[id]/delete` - request project teardown -- `GET | POST /api/projects/[id]/environment` - grouped env read and create/batch replace -- `PUT | DELETE /api/projects/[id]/environment/[envId]` - individual env edits -- `GET /api/sandbox/[id]/cwd` - terminal working directory lookup -- `POST /api/sandbox/[id]/exec` - detached sandbox command execution - -## Key Modules - -- `app/(dashboard)/projects/(list)/page.tsx` - projects overview -- `app/(dashboard)/projects/[id]/layout.tsx` - workspace shell and auth gate -- `components/layout/project-content-wrapper.tsx` - persistent terminal/content split -- `hooks/use-project.ts` - live project polling -- `hooks/use-project-operations.ts` - start/stop/delete client helper -- `hooks/use-environment-variables.ts` - environment variable CRUD -- `lib/actions/project.ts` - create/import server actions -- `lib/actions/database.ts` - optional database actions - -## Constraints - -- `/projects/[id]` is just a redirect to the terminal page. -- Current UI logic assumes one primary sandbox and at most one practical database, even though the schema is more flexible. -- Environment writes are rejected unless all sandboxes are already `RUNNING`. - diff --git a/docs/wiki/features/user-settings.md b/docs/wiki/features/user-settings.md deleted file mode 100644 index e63a1b8..0000000 --- a/docs/wiki/features/user-settings.md +++ /dev/null @@ -1,39 +0,0 @@ -# User Settings - -## Purpose - -This domain manages user-scoped configuration that affects all projects or the platform shell itself. - -## User-Facing Surfaces - -- `/settings` - redirects to integrations -- `/settings/integrations` - current settings page -- settings dialog opened from sidebars or project GitHub surfaces - -## Main Flows - -- Save a system prompt for AI-assisted development context. -- Validate and store kubeconfig for user-scoped Kubernetes access. -- Save Anthropic-compatible proxy configuration. -- Install or inspect the connected GitHub App account. - -## Supporting APIs - -- `GET | POST /api/user/config` -- `GET | POST /api/user/config/anthropic` -- `GET | POST /api/user/config/kc` -- `GET | POST /api/user/config/system-prompt` - -## Key Modules - -- `components/dialog/settings-dialog.tsx` - primary settings UI -- `app/(dashboard)/settings/integrations/page.tsx` - settings landing page -- `app/(dashboard)/settings/_components/github-status-card.tsx` - GitHub integration summary -- `provider/sealos.tsx` - Sealos environment detection -- `lib/k8s/k8s-service-helper.ts` - kubeconfig persistence and cache invalidation - -## Constraints - -- Only `/settings/integrations` is implemented in the v2.0 release branch. -- Kubeconfig is user-scoped, not project-scoped. -- In a Sealos environment the app can detect iframe context and treat kubeconfig differently from a normal browser session. diff --git a/docs/wiki/index.md b/docs/wiki/index.md deleted file mode 100644 index 2369143..0000000 --- a/docs/wiki/index.md +++ /dev/null @@ -1,47 +0,0 @@ -# Fulling Wiki - -## Project Summary - -Fulling is a single-repo Next.js application for creating and operating AI-assisted development sandboxes. A project always starts with a sandbox runtime and can optionally add a PostgreSQL database later. GitHub App installation, repository import, runtime control, and global skill rollout all hang off that core project model. - -The codebase combines a conventional App Router UI with a control plane that persists intent in PostgreSQL and lets background reconcile jobs bring Kubernetes resources into the expected state. - -## Current Routing Mode - -- Router mode: `app` -- UI routes live under `app/` -- API routes live under `app/api/` -- No Pages Router UI surface was found -- No `middleware.ts` was found - -See [Routing](./routing.md) for the route map and layout structure. - -## Main Business Domains - -- [Auth and Identity](./features/auth-and-identity.md) -- [Project Workspace](./features/project-workspace.md) -- [GitHub and Import](./features/github-and-import.md) -- [User Settings](./features/user-settings.md) -- [Global Skills](./features/global-skills.md) - -## Main Runtime Surfaces - -- UI and layouts: App Router pages under `app/` -- Internal API surface: authenticated route handlers under `app/api/` -- Server actions: write-side entrypoints under `lib/actions/` -- Control plane commands: durable intent handling under `lib/platform/control/commands/` -- Reconcile jobs and listeners: `lib/jobs/` and `lib/events/` -- External integrations: Kubernetes, Sealos, GitHub App, ttyd, and Anthropic proxy support - -## Wiki Map - -- [Architecture](./architecture.md) -- [Routing](./routing.md) -- [Rendering and Data Flow](./rendering-and-data-flow.md) -- [Auth and State](./auth-and-state.md) -- [Config and Env](./config-and-env.md) -- [Data Models](./data-models.md) -- [Integrations](./integrations.md) -- [Background Jobs](./background-jobs.md) -- [TODO](./todo.md) -- [API Index](./api/index.md) diff --git a/docs/wiki/integrations.md b/docs/wiki/integrations.md deleted file mode 100644 index 2c5c76c..0000000 --- a/docs/wiki/integrations.md +++ /dev/null @@ -1,101 +0,0 @@ -# Integrations - -## Overview - -The repository integrates with external systems through a mix of `lib/k8s/`, `lib/services/`, `lib/util/`, and the newer `lib/platform/integrations/` layer. - -The main integrations are: - -- Kubernetes -- Sealos -- GitHub App and GitHub OAuth -- ttyd -- Anthropic-compatible proxy settings - -## Kubernetes - -Kubernetes is the primary infrastructure backend for both sandbox and database resources. - -Core integration points: - -- `lib/k8s/k8s-service-helper.ts` - user-scoped service resolution from `KUBECONFIG` -- `lib/k8s/kubernetes-service-factory.ts` - service instance caching -- `lib/k8s/kubernetes.ts` - main service implementation -- `lib/platform/integrations/k8s/get-user-default-namespace.ts` - control-plane namespace lookup - -Kubernetes effects are intentionally kept out of the initial user mutation path. The control plane writes intent first, then reconcile jobs call the Kubernetes service later. - -## Sealos - -Sealos appears in two roles: - -1. embedded runtime detection through `provider/sealos.tsx` -2. auth bootstrap through the Sealos credentials provider in `lib/auth.ts` - -When the app detects a Sealos iframe, it can pull session data, kubeconfig, and namespace-like identity details from the Sealos desktop SDK. - -## GitHub App and GitHub OAuth - -GitHub integration covers three related needs: - -- user sign-in and token storage -- GitHub App installation tracking -- repository discovery and import - -Core modules: - -- `lib/services/github-app.ts` -- `app/api/github/app/callback/route.ts` -- `app/api/github/app/webhook/route.ts` -- `lib/actions/github.ts` -- `lib/platform/integrations/github/find-installation-repository.ts` - -The webhook currently updates installation lifecycle status. Repository import uses installation tokens rather than user PAT-like flows. - -## ttyd - -ttyd is the transport layer for sandbox command execution. - -Important modules: - -- `lib/util/ttyd-context.ts` -- `lib/util/ttyd-exec.ts` -- `app/api/sandbox/[id]/exec/route.ts` -- `app/api/sandbox/[id]/cwd/route.ts` -- `lib/jobs/project-task/executors/*` - -This is how the app: - -- runs background commands in the sandbox -- resolves current working directories -- clones GitHub repositories into the sandbox -- installs and uninstalls skills inside project workspaces - -## Anthropic Proxy Support - -User-level Anthropic settings are stored in `UserConfig`. `lib/services/aiproxy.ts` then: - -- creates proxy tokens when Sealos auth is used -- projects Anthropic env vars into sandboxes during create and update flows - -This lets sandboxes inherit AI configuration without storing those values directly in project env rows. - -## Emerging `lib/platform/integrations/` Layer - -Readmes under `lib/platform/integrations/` describe a cleaner boundary for provider-specific protocol logic. - -Currently visible integration slices: - -- `github/` -- `k8s/` -- `aiproxy/` -- `ttyd/` - -The repository still mixes old and new approaches, so these directories should be read as directionally important rather than fully exhaustive of all provider-facing code. - -## Constraints - -- GitHub organization installations are explicitly rejected in the callback flow today. -- ttyd-backed command execution is powerful and central to task execution, so sandbox availability is a hard prerequisite for import and skill rollout. -- Kubernetes access is entirely user-scoped. Without `KUBECONFIG`, project and database creation fail early. - diff --git a/docs/wiki/rendering-and-data-flow.md b/docs/wiki/rendering-and-data-flow.md deleted file mode 100644 index a606f90..0000000 --- a/docs/wiki/rendering-and-data-flow.md +++ /dev/null @@ -1,117 +0,0 @@ -# Rendering and Data Flow - -## Rendering Model - -The repository mixes Server Components for routing, access control, and initial reads with Client Components for long-lived UI state and runtime interaction. - -Server-driven examples: - -- `app/(landing)/page.tsx` fetches GitHub star count on the server. -- `app/(dashboard)/projects/(list)/page.tsx` checks the session and reads projects on the server. -- `app/(dashboard)/projects/[id]/layout.tsx` verifies ownership on the server before rendering the workspace shell. - -Client-driven examples: - -- project configuration pages under `app/(dashboard)/projects/[id]/*` -- `components/layout/project-content-wrapper.tsx` -- `components/dialog/settings-dialog.tsx` -- `app/(dashboard)/skills/_components/skills-library.tsx` - -## Data Fetching - -### Server Reads - -Server Components mainly use: - -- `auth()` from `lib/auth.ts` -- cached Prisma reads from `lib/data/project.ts` and `lib/data/user-skill.ts` - -These reads are request-scoped and do not try to keep live runtime state in sync after render. - -### Client Reads - -The client uses React Query for live state. - -- `provider/providers.tsx` creates the shared `QueryClient` -- `hooks/use-project.ts` polls `/api/projects/[id]` every 3 seconds by default -- `hooks/use-environment-variables.ts` reads and mutates environment variables through internal API routes - -The result is a split model: - -- server render for secure entry and initial HTML -- client polling for runtime status, task progress, and mutation refresh - -## Cache and Revalidation - -- Landing page fetches GitHub star count with `next: { revalidate: 3600 }`. -- Query client default `staleTime` is 5 seconds. -- `useProject` uses a 2 second stale window and a 3 second refetch interval. -- `useEnvironmentVariables` uses a 5 second stale window and no focus refetch. - -There is no repository-wide custom cache layer beyond React cache and React Query. - -## Mutations and Server Actions - -The repository uses both server actions and internal APIs. - -### Server Actions - -Used when the UI wants a direct write-side call: - -- `createProject` -- `importProjectFromGitHub` -- `createDatabase` -- `deleteDatabase` -- `enableGlobalSkill` -- `uninstallGlobalSkill` -- GitHub installation and repository listing helpers - -### Internal APIs - -Used when the UI needs explicit route contracts or browser fetch: - -- project polling and lifecycle control -- environment variable CRUD -- sandbox execution and status endpoints -- user configuration endpoints - -## Runtime State Flow - -### Project Workspace - -The project layout keeps a persistent terminal shell mounted through `ProjectContentWrapper`. - -- terminal state stays alive across route changes -- non-terminal pages render in a separate content panel -- route changes switch visibility instead of unmounting the terminal - -This is important because sandbox terminal state depends on long-lived websocket and ttyd interaction. - -### Settings and Forms - -Most forms use local component state plus mutation hooks or direct fetch calls. - -Common patterns: - -- `useState` for form fields and dialogs -- `useTransition` for optimistic UI around server actions -- `useMutation` for environment variable writes -- `toast` notifications for success and failure - -## Client State - -Shared client state is limited and intentional: - -- session state from NextAuth -- React Query cache -- Sealos iframe/session context -- theme selection via `next-themes` - -There is no broader global store such as Redux or Zustand in the current codebase. - -## Constraints - -- Project status is intentionally eventual. UI polling is the primary way users observe background reconcile progress. -- Environment writes are blocked unless the project and all sandboxes are already running. -- Some sandbox operations still live under route handlers even though `lib/actions/sandbox.ts` notes a future migration toward server actions. - diff --git a/docs/wiki/routing.md b/docs/wiki/routing.md deleted file mode 100644 index f89f62d..0000000 --- a/docs/wiki/routing.md +++ /dev/null @@ -1,145 +0,0 @@ -# Routing - -## Routing Overview - -Fulling uses the App Router only. The route tree is organized with route groups for landing, auth, and dashboard surfaces, plus a standalone GitHub callback page. - -Route groups: - -- `(landing)` - marketing and public entry surface -- `(auth)` - login and auth error views -- `(dashboard)` - authenticated application UI for projects, settings, and skills -- standalone `/github/app/callback` - popup-oriented GitHub App install completion page - -No `middleware.ts` was found, so route access is enforced in server components and API helpers rather than global middleware. - -## Layouts - -### Root Layout - -`app/layout.tsx` wraps the app in: - -- `SessionProvider` -- `QueryClientProvider` -- `SealosProvider` -- `ThemeProvider` -- global toaster notifications - -### Projects List Layout - -`app/(dashboard)/projects/(list)/layout.tsx` renders: - -- shared dashboard sidebar -- search bar -- projects list content - -### Project Detail Layout - -`app/(dashboard)/projects/[id]/layout.tsx` is the main workspace shell. - -It: - -- checks auth with `auth()` -- verifies the project belongs to the current user -- mounts a primary sidebar -- mounts a project-specific sidebar -- renders `ProjectContentWrapper`, which keeps the terminal mounted across navigation - -### Settings Layout - -`app/(dashboard)/settings/layout.tsx` renders the shared sidebar and a settings sidebar, then redirects the top-level settings page to `/settings/integrations`. - -### Skills Layout - -`app/(dashboard)/skills/layout.tsx` renders the shared dashboard sidebar and a single-column content area. - -## App Routes - -### Public and Pre-Auth Pages - -- `/` - landing page with server-fetched GitHub star count -- `/login` - credentials and GitHub sign-in UI -- `/auth-error` - auth error view -- `/github/app/callback` - popup page that calls the API callback and posts a message back to the opener - -### Dashboard Pages - -- `/projects` -- `/projects/[id]` - - redirects to `/projects/[id]/terminal` -- `/projects/[id]/terminal` -- `/projects/[id]/database` -- `/projects/[id]/environment` -- `/projects/[id]/secrets` -- `/projects/[id]/auth` -- `/projects/[id]/payment` -- `/projects/[id]/github` -- `/projects/[id]/exec-test` -- `/settings` - - redirects to `/settings/integrations` -- `/settings/integrations` -- `/skills` - -## API Routes - -API routes live under `app/api/` and are documented in [API Index](./api/index.md). - -Main route families: - -- `/api/auth/[...nextauth]` -- `/api/github/app/*` -- `/api/projects/[id]/*` -- `/api/sandbox/[id]/*` -- `/api/user/config/*` - -## Dynamic Segments - -UI dynamic segments: - -- `/projects/[id]` - -API dynamic segments: - -- `/api/auth/[...nextauth]` -- `/api/projects/[id]` -- `/api/projects/[id]/environment/[envId]` -- `/api/sandbox/[id]` - -## Navigation by Domain - -### Global Sidebar - -The shared dashboard sidebar exposes links for: - -- `/projects` -- `/skills` -- `/settings` - -The v2.0 release branch intentionally keeps unimplemented future surfaces such -as MCP, templates, and top-level integrations out of the visible navigation. - -### Project Sidebar - -The project workspace sidebar is split into: - -- Workspace - - terminal - - database -- Configuration - - environment - - secrets - - auth - - payment - - GitHub - -### Settings Sidebar - -The settings sidebar links to: - -- `/settings/integrations` - -## Route-Level Constraints - -- Project detail access is enforced in the project layout, not middleware. -- `/projects/[id]` is not a real overview page; it always redirects to the terminal page. -- `app/(dashboard)/projects/(list)/_components/project-actions-menu.tsx` points the Settings action to `/projects/[id]/environment`, the first concrete project configuration route. diff --git a/docs/wiki/todo.md b/docs/wiki/todo.md deleted file mode 100644 index e2fe081..0000000 --- a/docs/wiki/todo.md +++ /dev/null @@ -1,27 +0,0 @@ -# TODO - -## Overview - -This page collects code-backed follow-up work that is visible from the current repository state. These items are not a roadmap promise. They are simply the most obvious gaps and cleanup points surfaced while generating the wiki. - -## Navigation Gaps - -- Keep hidden future surfaces such as MCP, templates, and top-level integrations out of the v2.0 navigation until they have implemented routes. -- If `/settings/account` is added later, reintroduce it to `app/(dashboard)/settings/_components/settings-sidebar.tsx`. - -## Project Workspace Follow-Ups - -- Decide whether `/projects/[id]/exec-test` is a permanent product surface or a debug-only page, then align navigation and docs accordingly. -- Review the current assumption that the first sandbox and first database are the primary runtime surfaces, even though the schema supports more than one of each. -- Revisit the environment update rule that blocks writes unless the full project is already `RUNNING`, and confirm that this is the intended UX for setup flows. - -## API and Control Plane Follow-Ups - -- Add stronger request validation around internal route handlers, which currently rely mostly on inline checks instead of shared schemas. -- Finish the migration noted in `lib/actions/sandbox.ts` so sandbox operations do not remain split across route handlers and server actions. -- Continue consolidating platform logic into `lib/platform/` so the newer control-plane layering becomes the clear source of truth. - -## Documentation Follow-Ups - -- Keep this page in sync when missing routes are implemented or reintroduced. -- If task priority starts to matter, replace this flat list with a status-driven backlog page rather than mixing implementation state into architecture pages. diff --git a/instrumentation.ts b/instrumentation.ts index 27395a4..daf96e2 100644 --- a/instrumentation.ts +++ b/instrumentation.ts @@ -18,16 +18,16 @@ const logger = baseLogger.child({ module: 'instrumentation' }) export async function register() { // Only run on the server side if (process.env.NEXT_RUNTIME === 'nodejs') { - logger.info('🚀 Application initialization started') + logger.info('[START] Application initialization started') try { // Import and initialize events and jobs const { initializeApp } = await import('@/lib/startup') await initializeApp() - logger.info('✅ Application initialization completed') + logger.info('[DONE] Application initialization completed') } catch (error) { - logger.error(`❌ Application initialization failed: ${error}`) + logger.error(`[ERROR] Application initialization failed: ${error}`) // Don't throw error to prevent app from crashing // Jobs will retry on next reconcile cycle } diff --git a/package.json b/package.json index 3071439..c7d532f 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,8 @@ "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", "@t3-oss/env-nextjs": "^0.13.8", - "@tanstack/react-query": "^5.90.7", - "@tanstack/react-query-devtools": "^5.90.2", + "@tanstack/react-query": "^5.100.10", + "@tanstack/react-query-devtools": "^5.100.10", "@xterm/addon-canvas": "^0.7.0", "@xterm/addon-fit": "^0.10.0", "@xterm/addon-web-links": "^0.11.0", @@ -70,7 +70,7 @@ "zod": "^4.1.12" }, "devDependencies": { - "@tailwindcss/postcss": "^4", + "@tailwindcss/postcss": "^4.3.0", "@types/jsonwebtoken": "^9.0.10", "@types/node": "^20", "@types/react": "19.2.7", @@ -82,7 +82,7 @@ "eslint-config-prettier": "^10.1.8", "eslint-plugin-simple-import-sort": "^12.1.1", "prettier": "^3.7.4", - "tailwindcss": "^4", + "tailwindcss": "^4.3.0", "tw-animate-css": "^1.4.0", "typescript": "^5", "vite-tsconfig-paths": "^6.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c2564c3..7bd56fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,11 +69,11 @@ importers: specifier: ^0.13.8 version: 0.13.10(typescript@5.9.3)(zod@4.2.1) '@tanstack/react-query': - specifier: ^5.90.7 - version: 5.90.12(react@19.2.1) + specifier: ^5.100.10 + version: 5.100.10(react@19.2.1) '@tanstack/react-query-devtools': - specifier: ^5.90.2 - version: 5.91.1(@tanstack/react-query@5.90.12(react@19.2.1))(react@19.2.1) + specifier: ^5.100.10 + version: 5.100.10(@tanstack/react-query@5.100.10(react@19.2.1))(react@19.2.1) '@xterm/addon-canvas': specifier: ^0.7.0 version: 0.7.0(@xterm/xterm@5.5.0) @@ -157,8 +157,8 @@ importers: version: 4.2.1 devDependencies: '@tailwindcss/postcss': - specifier: ^4 - version: 4.1.18 + specifier: ^4.3.0 + version: 4.3.0 '@types/jsonwebtoken': specifier: ^9.0.10 version: 9.0.10 @@ -193,8 +193,8 @@ importers: specifier: ^3.7.4 version: 3.7.4 tailwindcss: - specifier: ^4 - version: 4.1.18 + specifier: ^4.3.0 + version: 4.3.0 tw-animate-css: specifier: ^1.4.0 version: 1.4.0 @@ -1653,65 +1653,65 @@ packages: zod: optional: true - '@tailwindcss/node@4.1.18': - resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} + '@tailwindcss/node@4.3.0': + resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==} - '@tailwindcss/oxide-android-arm64@4.1.18': - resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-android-arm64@4.3.0': + resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==} + engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.1.18': - resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-darwin-arm64@4.3.0': + resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==} + engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.1.18': - resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-darwin-x64@4.3.0': + resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==} + engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.1.18': - resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-freebsd-x64@4.3.0': + resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==} + engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': - resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==} + engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': - resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.1.18': - resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.1.18': - resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.1.18': - resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-wasm32-wasi@4.1.18': - resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -1722,39 +1722,39 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': - resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==} + engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.1.18': - resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==} + engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.1.18': - resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} - engines: {node: '>= 10'} + '@tailwindcss/oxide@4.3.0': + resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==} + engines: {node: '>= 20'} - '@tailwindcss/postcss@4.1.18': - resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==} + '@tailwindcss/postcss@4.3.0': + resolution: {integrity: sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==} - '@tanstack/query-core@5.90.12': - resolution: {integrity: sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg==} + '@tanstack/query-core@5.100.10': + resolution: {integrity: sha512-8UR0yJR+GiQ40m3lPhUr0xbfAupe6GSQiksSBSa9SM2NjezFyxXCIA69/lz8cSoNKZLrw1/PktIyQBJcVeMi3w==} - '@tanstack/query-devtools@5.91.1': - resolution: {integrity: sha512-l8bxjk6BMsCaVQH6NzQEE/bEgFy1hAs5qbgXl0xhzezlaQbPk6Mgz9BqEg2vTLPOHD8N4k+w/gdgCbEzecGyNg==} + '@tanstack/query-devtools@5.100.10': + resolution: {integrity: sha512-3DmJf25hDPus5IpVvp6ujXv6bKV2zPzI9vpbAmpJigsL/H6DPvPjmf7/Q9yVKEke//8fgeQ45abjgnLuyYxAiw==} - '@tanstack/react-query-devtools@5.91.1': - resolution: {integrity: sha512-tRnJYwEbH0kAOuToy8Ew7bJw1lX3AjkkgSlf/vzb+NpnqmHPdWM+lA2DSdGQSLi1SU0PDRrrCI1vnZnci96CsQ==} + '@tanstack/react-query-devtools@5.100.10': + resolution: {integrity: sha512-zes0+o9ef5rAZXJ9f/SeaLs2nufJaeVkZkl/Or9NGrWVF41kL9Od9ED9nCwtQlgiF2VGtrzhEw5AU/igAO+aAg==} peerDependencies: - '@tanstack/react-query': ^5.90.10 + '@tanstack/react-query': ^5.100.10 react: ^18 || ^19 - '@tanstack/react-query@5.90.12': - resolution: {integrity: sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg==} + '@tanstack/react-query@5.100.10': + resolution: {integrity: sha512-FLaZf2RCrA/Zgp4aiu5tG3TyasTRO7aZ99skxQpr3Hg/zXOhu6yq5FZCYQ/tRaJtM9ylnoK8tFK7PolXQadv6Q==} peerDependencies: react: ^18 || ^19 @@ -2392,8 +2392,8 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - enhanced-resolve@5.18.4: - resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + enhanced-resolve@5.21.3: + resolution: {integrity: sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==} engines: {node: '>=10.13.0'} es-abstract@1.24.1: @@ -2993,142 +2993,72 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lightningcss-android-arm64@1.30.2: - resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [android] - lightningcss-darwin-arm64@1.30.2: - resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - lightningcss-darwin-arm64@1.32.0: resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.30.2: - resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - lightningcss-darwin-x64@1.32.0: resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.30.2: - resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - lightningcss-freebsd-x64@1.32.0: resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.30.2: - resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - lightningcss-linux-arm-gnueabihf@1.32.0: resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.30.2: - resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - lightningcss-linux-arm64-gnu@1.32.0: resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-arm64-musl@1.30.2: - resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-x64-gnu@1.30.2: - resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-linux-x64-musl@1.30.2: - resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-win32-arm64-msvc@1.30.2: - resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.30.2: - resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - lightningcss-win32-x64-msvc@1.32.0: resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.30.2: - resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} - engines: {node: '>= 12.0.0'} - lightningcss@1.32.0: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} @@ -3438,8 +3368,8 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + postcss@8.5.14: + resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} engines: {node: ^10 || ^12 || >=14} postcss@8.5.8: @@ -3802,11 +3732,11 @@ packages: tailwind-merge@3.4.0: resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} - tailwindcss@4.1.18: - resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + tailwindcss@4.3.0: + resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} - tapable@2.3.0: - resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} tar-fs@3.1.1: @@ -5587,88 +5517,88 @@ snapshots: typescript: 5.9.3 zod: 4.2.1 - '@tailwindcss/node@4.1.18': + '@tailwindcss/node@4.3.0': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.18.4 + enhanced-resolve: 5.21.3 jiti: 2.6.1 - lightningcss: 1.30.2 + lightningcss: 1.32.0 magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.1.18 + tailwindcss: 4.3.0 - '@tailwindcss/oxide-android-arm64@4.1.18': + '@tailwindcss/oxide-android-arm64@4.3.0': optional: true - '@tailwindcss/oxide-darwin-arm64@4.1.18': + '@tailwindcss/oxide-darwin-arm64@4.3.0': optional: true - '@tailwindcss/oxide-darwin-x64@4.1.18': + '@tailwindcss/oxide-darwin-x64@4.3.0': optional: true - '@tailwindcss/oxide-freebsd-x64@4.1.18': + '@tailwindcss/oxide-freebsd-x64@4.3.0': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.1.18': + '@tailwindcss/oxide-linux-x64-musl@4.3.0': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.1.18': + '@tailwindcss/oxide-wasm32-wasi@4.3.0': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': optional: true - '@tailwindcss/oxide@4.1.18': + '@tailwindcss/oxide@4.3.0': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.18 - '@tailwindcss/oxide-darwin-arm64': 4.1.18 - '@tailwindcss/oxide-darwin-x64': 4.1.18 - '@tailwindcss/oxide-freebsd-x64': 4.1.18 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 - '@tailwindcss/oxide-linux-x64-musl': 4.1.18 - '@tailwindcss/oxide-wasm32-wasi': 4.1.18 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 - - '@tailwindcss/postcss@4.1.18': + '@tailwindcss/oxide-android-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-x64': 4.3.0 + '@tailwindcss/oxide-freebsd-x64': 4.3.0 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.0 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.0 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-x64-musl': 4.3.0 + '@tailwindcss/oxide-wasm32-wasi': 4.3.0 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 + + '@tailwindcss/postcss@4.3.0': dependencies: '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.1.18 - '@tailwindcss/oxide': 4.1.18 - postcss: 8.5.6 - tailwindcss: 4.1.18 + '@tailwindcss/node': 4.3.0 + '@tailwindcss/oxide': 4.3.0 + postcss: 8.5.14 + tailwindcss: 4.3.0 - '@tanstack/query-core@5.90.12': {} + '@tanstack/query-core@5.100.10': {} - '@tanstack/query-devtools@5.91.1': {} + '@tanstack/query-devtools@5.100.10': {} - '@tanstack/react-query-devtools@5.91.1(@tanstack/react-query@5.90.12(react@19.2.1))(react@19.2.1)': + '@tanstack/react-query-devtools@5.100.10(@tanstack/react-query@5.100.10(react@19.2.1))(react@19.2.1)': dependencies: - '@tanstack/query-devtools': 5.91.1 - '@tanstack/react-query': 5.90.12(react@19.2.1) + '@tanstack/query-devtools': 5.100.10 + '@tanstack/react-query': 5.100.10(react@19.2.1) react: 19.2.1 - '@tanstack/react-query@5.90.12(react@19.2.1)': + '@tanstack/react-query@5.100.10(react@19.2.1)': dependencies: - '@tanstack/query-core': 5.90.12 + '@tanstack/query-core': 5.100.10 react: 19.2.1 '@tybys/wasm-util@0.10.1': @@ -6322,10 +6252,10 @@ snapshots: dependencies: once: 1.4.0 - enhanced-resolve@5.18.4: + enhanced-resolve@5.21.3: dependencies: graceful-fs: 4.2.11 - tapable: 2.3.0 + tapable: 2.3.3 es-abstract@1.24.1: dependencies: @@ -7069,88 +6999,39 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lightningcss-android-arm64@1.30.2: - optional: true - lightningcss-android-arm64@1.32.0: optional: true - lightningcss-darwin-arm64@1.30.2: - optional: true - lightningcss-darwin-arm64@1.32.0: optional: true - lightningcss-darwin-x64@1.30.2: - optional: true - lightningcss-darwin-x64@1.32.0: optional: true - lightningcss-freebsd-x64@1.30.2: - optional: true - lightningcss-freebsd-x64@1.32.0: optional: true - lightningcss-linux-arm-gnueabihf@1.30.2: - optional: true - lightningcss-linux-arm-gnueabihf@1.32.0: optional: true - lightningcss-linux-arm64-gnu@1.30.2: - optional: true - lightningcss-linux-arm64-gnu@1.32.0: optional: true - lightningcss-linux-arm64-musl@1.30.2: - optional: true - lightningcss-linux-arm64-musl@1.32.0: optional: true - lightningcss-linux-x64-gnu@1.30.2: - optional: true - lightningcss-linux-x64-gnu@1.32.0: optional: true - lightningcss-linux-x64-musl@1.30.2: - optional: true - lightningcss-linux-x64-musl@1.32.0: optional: true - lightningcss-win32-arm64-msvc@1.30.2: - optional: true - lightningcss-win32-arm64-msvc@1.32.0: optional: true - lightningcss-win32-x64-msvc@1.30.2: - optional: true - lightningcss-win32-x64-msvc@1.32.0: optional: true - lightningcss@1.30.2: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.30.2 - lightningcss-darwin-arm64: 1.30.2 - lightningcss-darwin-x64: 1.30.2 - lightningcss-freebsd-x64: 1.30.2 - lightningcss-linux-arm-gnueabihf: 1.30.2 - lightningcss-linux-arm64-gnu: 1.30.2 - lightningcss-linux-arm64-musl: 1.30.2 - lightningcss-linux-x64-gnu: 1.30.2 - lightningcss-linux-x64-musl: 1.30.2 - lightningcss-win32-arm64-msvc: 1.30.2 - lightningcss-win32-x64-msvc: 1.30.2 - lightningcss@1.32.0: dependencies: detect-libc: 2.1.2 @@ -7452,7 +7333,7 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.6: + postcss@8.5.14: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -7938,9 +7819,9 @@ snapshots: tailwind-merge@3.4.0: {} - tailwindcss@4.1.18: {} + tailwindcss@4.3.0: {} - tapable@2.3.0: {} + tapable@2.3.3: {} tar-fs@3.1.1: dependencies: diff --git a/runtime/README.md b/runtime/README.md index 9e30d79..9145878 100644 --- a/runtime/README.md +++ b/runtime/README.md @@ -406,9 +406,9 @@ MIT License - See LICENSE file in the repository root ## Support -- **Issues**: https://github.com/FullstackAgent/FullstackAgent/issues -- **Discussions**: https://github.com/FullstackAgent/FullstackAgent/discussions -- **Documentation**: https://github.com/FullstackAgent/FullstackAgent/tree/main/docs +- **Issues**: https://github.com/FullAgent/fulling/issues +- **Discussions**: https://github.com/FullAgent/fulling/discussions +- **Repository documentation**: ../.qoder/repowiki ## Contributing @@ -426,7 +426,8 @@ The CI/CD workflow will automatically validate your changes. ## Changelog -See [docs/changelogs/](../docs/changelogs/) for version history and changes. +See GitHub Releases for durable release history: +https://github.com/FullAgent/fulling/releases ## Maintainers @@ -437,4 +438,4 @@ See [docs/changelogs/](../docs/changelogs/) for version history and changes. **Built with Claude Code for AI-powered full-stack development** -Multi-architecture support powered by GitHub Actions and Docker Buildx \ No newline at end of file +Multi-architecture support powered by GitHub Actions and Docker Buildx diff --git a/yaml/.env.template b/yaml/.env.template deleted file mode 100644 index 99fbff8..0000000 --- a/yaml/.env.template +++ /dev/null @@ -1,13 +0,0 @@ -DATABASE_URL="" -KUBECONFIG_PATH="" - -ANTHROPIC_BASE_URL= -ANTHROPIC_API_KEY= - -# NextAuth Configuration -NEXTAUTH_URL="http://localhost:3000" -NEXTAUTH_SECRET="" - -# GitHub OAuth (replace with your actual values) -GITHUB_CLIENT_ID="" -GITHUB_CLIENT_SECRET="" \ No newline at end of file diff --git a/yaml/README.md b/yaml/README.md deleted file mode 100644 index f11badb..0000000 --- a/yaml/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Database and Sandbox Creation Rules - -Note: When calling K8s interface to create deployment service ingress and other objects, strictly reference the YAML files in this directory. Do not omit important information inside, such as label information and other fields. Except for some necessary fields that have changed, other general content needs to be retained. - -All resources should have naming rules, such as [project-name]-agentruntime-[6-digit-random-number] \ No newline at end of file diff --git a/yaml/database/README.md b/yaml/database/README.md deleted file mode 100644 index 9b94fe0..0000000 --- a/yaml/database/README.md +++ /dev/null @@ -1,39 +0,0 @@ -## YAML Example for Starting Database - -You can refer to the YAML file to call the Kubernetes API to implement database creation, and get the internal address and password of the database through `service` `secret` and other interfaces. - -To get the password, call the Kubernetes API to get the password field in the secret like [project-name]-conn-credential, which is base64 encoded, pay attention to parsing: - -Example of getting it: -``` -root@(ns-ajno7yq7) ~$ kubectl get secret fullstackagent-conn-credential -oyaml -apiVersion: v1 -data: - endpoint: ZnVsbHN0YWNrYWdlbnQtcG9zdyZXNx1NDMy - host: ZnVsbHN0YWNrYWdlbnQtcG9zdGdXNxbA== - password: bm43eHB2M= - port: NTQzM== - username: cGdGdyZXM= -kind: Secret -metadata: - creationTimestamp: "2025-10-11T04:33:32Z" - finalizers: - - cluster.kubeblocks.io/finalizer - labels: - app.kubernetes.io/instance: fullstackagent - app.kubernetes.io/managed-by: kubeblocks - app.kubernetes.io/name: postgresql - apps.kubeblocks.io/cluster-type: postgresql - name: fullstackagent-conn-credential - namespace: ns-ajno7yq7 - ownerReferences: - - apiVersion: apps.kubeblocks.io/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: Cluster - name: fullstackagent - uid: 7d246a6b-4714-4861-bdb8-c1c95a28898c - resourceVersion: "612201031" - uid: b714f062-88ce-45aa-820d-b37f4078bb20 -type: Opaque -``` \ No newline at end of file diff --git a/yaml/database/account.yaml b/yaml/database/account.yaml deleted file mode 100644 index d453ef3..0000000 --- a/yaml/database/account.yaml +++ /dev/null @@ -1,42 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - sealos-db-provider-cr: fullstack - app.kubernetes.io/instance: fullstack - app.kubernetes.io/managed-by: kbcli - name: fullstack - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - sealos-db-provider-cr: fullstack - app.kubernetes.io/instance: fullstack - app.kubernetes.io/managed-by: kbcli - name: fullstack -rules: - - apiGroups: - - '*' - resources: - - '*' - verbs: - - '*' - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - sealos-db-provider-cr: fullstack - app.kubernetes.io/instance: fullstack - app.kubernetes.io/managed-by: kbcli - name: fullstack -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: fullstack -subjects: - - kind: ServiceAccount - name: fullstack \ No newline at end of file diff --git a/yaml/database/cluster.yaml b/yaml/database/cluster.yaml deleted file mode 100644 index 9203924..0000000 --- a/yaml/database/cluster.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: apps.kubeblocks.io/v1alpha1 -kind: Cluster -metadata: - finalizers: - - cluster.kubeblocks.io/finalizer - labels: - clusterdefinition.kubeblocks.io/name: postgresql - clusterversion.kubeblocks.io/name: postgresql-14.8.0 - sealos-db-provider-cr: fullstack - annotations: {} - name: fullstack -spec: - affinity: - nodeLabels: {} - podAntiAffinity: Preferred - tenancy: SharedNode - topologyKeys: - - kubernetes.io/hostname - clusterDefinitionRef: postgresql - clusterVersionRef: postgresql-14.8.0 - componentSpecs: - - componentDefRef: postgresql - monitor: true - name: postgresql - noCreatePDB: false - replicas: 1 - resources: - limits: - cpu: 1000m - memory: 1024Mi - requests: - cpu: 100m - memory: 102Mi - serviceAccountName: fullstack - switchPolicy: - type: Noop - volumeClaimTemplates: - - name: data - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 3Gi - storageClassName: openebs-backup - terminationPolicy: Delete - tolerations: [] \ No newline at end of file diff --git a/yaml/sandbox/ingress.yaml b/yaml/sandbox/ingress.yaml deleted file mode 100644 index 1356493..0000000 --- a/yaml/sandbox/ingress.yaml +++ /dev/null @@ -1,109 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: network-klahtiapwdxa - labels: - cloud.sealos.io/app-deploy-manager: FullstackSandbox - cloud.sealos.io/app-deploy-manager-domain: aeqnmvugspcz - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/proxy-body-size: 32m - nginx.ingress.kubernetes.io/ssl-redirect: 'false' - nginx.ingress.kubernetes.io/backend-protocol: HTTP - nginx.ingress.kubernetes.io/client-body-buffer-size: 64k - nginx.ingress.kubernetes.io/proxy-buffer-size: 64k - nginx.ingress.kubernetes.io/proxy-send-timeout: '300' - nginx.ingress.kubernetes.io/proxy-read-timeout: '300' - nginx.ingress.kubernetes.io/server-snippet: | - client_header_buffer_size 64k; - large_client_header_buffers 4 128k; -spec: - rules: - - host: aeqnmvugspcz.usw.sealos.io - http: - paths: - - pathType: Prefix - path: / - backend: - service: - name: FullstackSandbox-kcbnvqgwwqhb - port: - number: 3000 - tls: - - hosts: - - aeqnmvugspcz.usw.sealos.io - secretName: wildcard-cert - ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: network-qdelovavnead - labels: - cloud.sealos.io/app-deploy-manager: FullstackSandbox - cloud.sealos.io/app-deploy-manager-domain: pmvcptgdiaat - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/proxy-body-size: 32m - nginx.ingress.kubernetes.io/ssl-redirect: 'false' - nginx.ingress.kubernetes.io/backend-protocol: HTTP - nginx.ingress.kubernetes.io/client-body-buffer-size: 64k - nginx.ingress.kubernetes.io/proxy-buffer-size: 64k - nginx.ingress.kubernetes.io/proxy-send-timeout: '300' - nginx.ingress.kubernetes.io/proxy-read-timeout: '300' - nginx.ingress.kubernetes.io/server-snippet: | - client_header_buffer_size 64k; - large_client_header_buffers 4 128k; -spec: - rules: - - host: pmvcptgdiaat.usw.sealos.io - http: - paths: - - pathType: Prefix - path: / - backend: - service: - name: FullstackSandbox-kcbnvqgwwqhb - port: - number: 5000 - tls: - - hosts: - - pmvcptgdiaat.usw.sealos.io - secretName: wildcard-cert - ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: network-dvvlehxmlcte - labels: - cloud.sealos.io/app-deploy-manager: FullstackSandbox - cloud.sealos.io/app-deploy-manager-domain: qaliclnpvjcd - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/proxy-body-size: 32m - nginx.ingress.kubernetes.io/ssl-redirect: 'false' - nginx.ingress.kubernetes.io/backend-protocol: HTTP - nginx.ingress.kubernetes.io/client-body-buffer-size: 64k - nginx.ingress.kubernetes.io/proxy-buffer-size: 64k - nginx.ingress.kubernetes.io/proxy-send-timeout: '300' - nginx.ingress.kubernetes.io/proxy-read-timeout: '300' - nginx.ingress.kubernetes.io/server-snippet: | - client_header_buffer_size 64k; - large_client_header_buffers 4 128k; -spec: - rules: - - host: qaliclnpvjcd.usw.sealos.io - http: - paths: - - pathType: Prefix - path: / - backend: - service: - name: FullstackSandbox-kcbnvqgwwqhb - port: - number: 8080 - tls: - - hosts: - - qaliclnpvjcd.usw.sealos.io - secretName: wildcard-cert diff --git a/yaml/sandbox/service.yaml b/yaml/sandbox/service.yaml deleted file mode 100644 index 0518e57..0000000 --- a/yaml/sandbox/service.yaml +++ /dev/null @@ -1,23 +0,0 @@ - -apiVersion: v1 -kind: Service -metadata: - name: FullstackSandbox-kcbnvqgwwqhb - labels: - cloud.sealos.io/app-deploy-manager: FullstackSandbox # This label cannot be omitted -spec: - ports: - - port: 3000 - targetPort: 3000 - name: bnxxvusqruwc - protocol: TCP - - port: 5000 - targetPort: 5000 - name: hffzmqlmkwdx - protocol: TCP - - port: 8080 - targetPort: 8080 - name: hnkrdxqqlbuq - protocol: TCP - selector: - app: FullstackSandbox \ No newline at end of file diff --git a/yaml/sandbox/statefulset.yaml b/yaml/sandbox/statefulset.yaml deleted file mode 100644 index c775469..0000000 --- a/yaml/sandbox/statefulset.yaml +++ /dev/null @@ -1,69 +0,0 @@ -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: FullstackSandbox - annotations: - originImageName: fullstackagent/fullstack-web-runtime:latest - deploy.cloud.sealos.io/minReplicas: '1' - deploy.cloud.sealos.io/maxReplicas: '1' - deploy.cloud.sealos.io/resize: 5Gi - labels: - cloud.sealos.io/app-deploy-manager: FullstackSandbox - app: FullstackSandbox -spec: - replicas: 1 - revisionHistoryLimit: 1 - serviceName: FullstackSandbox-service - selector: - matchLabels: - app: FullstackSandbox - updateStrategy: - type: RollingUpdate - rollingUpdate: - maxUnavailable: 50% - minReadySeconds: 10 - template: - metadata: - labels: - app: FullstackSandbox - restartTime: '20251011113341' - spec: - automountServiceAccountToken: false - terminationGracePeriodSeconds: 10 - containers: - - name: FullstackSandbox - image: fullstackagent/fullstack-web-runtime:latest - env: [] - resources: - requests: - cpu: 20m - memory: 25Mi - limits: - cpu: 200m - memory: 256Mi - ports: - - containerPort: 3000 - name: bnxxvusqruwc - - containerPort: 5000 - name: hffzmqlmkwdx - - containerPort: 7681 - name: ttyd-port - - containerPort: 8080 - name: hnkrdxqqlbuq - imagePullPolicy: Always - volumeMounts: - - name: vn-homevn-fulling - mountPath: /home/fulling - volumes: [] - volumeClaimTemplates: - - metadata: - annotations: - path: /home/fulling - value: '5' - name: vn-homevn-fulling - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 5Gi \ No newline at end of file diff --git a/yaml/sandbox/terminal_ws.yaml b/yaml/sandbox/terminal_ws.yaml deleted file mode 100644 index e65db78..0000000 --- a/yaml/sandbox/terminal_ws.yaml +++ /dev/null @@ -1,35 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: network-ghypnddrnytm - labels: - cloud.sealos.io/app-deploy-manager: ttyd - cloud.sealos.io/app-deploy-manager-domain: hmpwkcrkvpga - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/proxy-body-size: 32m - nginx.ingress.kubernetes.io/ssl-redirect: 'false' - nginx.ingress.kubernetes.io/backend-protocol: HTTP - nginx.ingress.kubernetes.io/client-body-buffer-size: 64k - nginx.ingress.kubernetes.io/proxy-buffer-size: 64k - nginx.ingress.kubernetes.io/proxy-send-timeout: '300' - nginx.ingress.kubernetes.io/proxy-read-timeout: '300' - nginx.ingress.kubernetes.io/server-snippet: | - client_header_buffer_size 64k; - large_client_header_buffers 4 128k; -spec: - rules: - - host: hmpwkcrkvpga.usw.sealos.io - http: - paths: - - pathType: Prefix - path: / - backend: - service: - name: ttyd-ufhdxlkadbic - port: - number: 7681 - tls: - - hosts: - - hmpwkcrkvpga.usw.sealos.io - secretName: wildcard-cert From 4ddb981cccfd51ec81ee94b25a5519b2c65f616b Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Tue, 23 Jun 2026 17:04:06 +0800 Subject: [PATCH 3/7] docs: refresh v3 workspace direction (#163) --- AGENTS.md | 26 +- README.md | 289 ++++----- design/DESIGN.md | 289 --------- design/style-ethereal.html | 1188 ------------------------------------ docs/architecture.md | 170 ++++++ 5 files changed, 289 insertions(+), 1673 deletions(-) delete mode 100644 design/DESIGN.md delete mode 100644 design/style-ethereal.html create mode 100644 docs/architecture.md diff --git a/AGENTS.md b/AGENTS.md index 49fca18..cda43b4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,7 @@ This file provides guidance to AI agents when working with code in this reposito **Fulling v3.0** is building **dedicated AI workspaces** — persistent, personalizable environments with skills, files, memory, scripts, and runtime. Think of it as an **out-of-the-box AI toolkit** that users can customize and share. -Before making any UI changes, read [design/DESIGN.md](./design/DESIGN.md) for the complete design system specification. +Read [docs/architecture.md](./docs/architecture.md) before product or architecture work. It is the source of truth for the v3 workspace model. ## Tech Stack @@ -23,28 +23,26 @@ Before making any UI changes, read [design/DESIGN.md](./design/DESIGN.md) for th - Route-specific components in `_components/` directory - Shared components in top-level `components/` -## Architecture Patterns +## Current Implementation Context -- **Asynchronous reconciliation**: API → Database → Reconciliation Job → Event → K8s Operation +- Existing code still contains v2 project/resource abstractions. Do not treat them as v3 product architecture. +- **Asynchronous reconciliation** exists in current code: API → Database → Reconciliation Job → Event → K8s Operation - **Always use user-specific K8s service**: `const k8sService = await getK8sServiceForUser(userId)` - **Optimistic locking** in Repository layer - **Non-blocking APIs**: endpoints only update DB, return immediately -## Anti-Patterns (Never Do) +## UI Direction -- No emojis anywhere in the UI -- No Inter font -- No generic serif fonts -- No pure black (`#000000`) -- No purple/neon glows -- No oversaturated accents -- No 3-column equal card layouts -- No centered Hero sections (use asymmetric splits) -- No AI copywriting cliches ("Elevate", "Seamless", "Unleash") +There is no active v3 design system in the repository. The previous design draft +has been removed. Before substantial UI work, define or confirm the current +visual direction instead of inheriting old visual rules. + +Avoid generic AI copywriting cliches such as "Elevate", "Seamless", and +"Unleash". ## Key Files -- [design/DESIGN.md](./design/DESIGN.md) — Design system (read before any UI work) +- [docs/architecture.md](./docs/architecture.md) — v3 system architecture and product model - `lib/k8s/k8s-service-helper.ts` — User-specific K8s service - `lib/events/` + `lib/jobs/` — Reconciliation core - `instrumentation.ts` — Application startup diff --git a/README.md b/README.md index 7e7a38c..675f3e4 100644 --- a/README.md +++ b/README.md @@ -1,188 +1,141 @@ # Fulling
- Version 3.0 vision - Version 2.0.0 released - Next.js - TypeScript - Kubernetes - Claude Code + Fulling + +

Package AI workspaces once. Share them with people who just need the AI to work.

+ +

+ Fulling v3.0 workspace delivery + Fulling v2.0.0 released + Next.js 16 + TypeScript 5 +

+ +

+ Overview • + Workspace model • + How it works • + Project status • + Local development +

-## What Is Fulling? +## Overview -**Fulling turns AI from a conversation into a working environment.** +Fulling is a product for creating ready-to-use AI workspaces. -Most AI products start and end with a prompt. Fulling is built around a more -durable idea: an AI that has its own skills, files, memory, scripts, and -runtime. +It is built around a practical problem: many professionals can benefit from AI, +but they do not want to configure skills, prompts, memory, scripts, integrations, +or runtime settings. Fulling gives creators a place to assemble those pieces, +then share the finished workspace with the person who needs it. -The v3.0 direction is to make these environments personal, persistent, and easy -to give to someone else: +> [!NOTE] +> Fulling v3.0 centers on workspace delivery: the setup work happens once, and +> the recipient opens a prepared AI workspace instead of learning the AI stack. -> Give an AI a mission, the materials it needs, the tools it can use, and a -> runtime where it can work. Then hand it to the person or project it was made -> for. +## Why Fulling? -## Why This Matters +Most AI tools start with an empty chat box. That works for people who already +know how to prompt, configure tools, and judge the outputs. It breaks down when +the user is a domain expert who simply needs help with real work. -Most AI products package behavior as prompts. Prompts are easy to copy, but they -are weak as durable work environments: +Fulling turns that setup into a deliverable: -- They do not own files. -- They do not naturally keep inspectable memory. -- They do not have scripts for repeatable work. -- They do not have a runtime where real tools can execute. -- They are hard to hand to another person as a long-term assistant. +- a building designer receives a workspace with drawing review workflows, + project references, and report templates +- a consultant receives a workspace with client materials, analysis scripts, + and reusable delivery formats +- a team receives a workspace with shared context, approved tools, and repeatable + operating routines -Fulling is built around a different object: **a dedicated AI workspace**. +The recipient uses the workspace. The creator owns the configuration. -That workspace should feel like giving someone a small, persistent AI operator -that already understands its job, owns its materials, and can keep working after -the initial setup session ends. +## Workspace Model -## The 3.0 Direction +An AI workspace combines the context, capabilities, and runtime needed for a +specific person or job. -The internal bar for 3.0 is speed and clarity: creating one of these AI -environments should feel casual enough to do for a friend, teammate, customer, -or project. +| Part | Purpose | +| --- | --- | +| Mission | The job this AI workspace exists to help with | +| Knowledge | Files, notes, examples, references, and domain material | +| Memory | Durable context that can evolve over time | +| Skills | Named capabilities the AI can use | +| Scripts | Repeatable actions for work that needs reliability | +| Runtime | A place where tools, code, and automation can run | +| Sharing | A way to hand the configured workspace to another user | -The intended shape: - -1. Choose who this AI is for. -2. Describe what this AI should help with. -3. Add files, notes, examples, and domain knowledge. -4. Select or generate skills and scripts. -5. Start the runtime. -6. Test the AI in its own workspace. -7. Share it with the person it was made for. - -The output is not just a chat configuration. The output is a living workspace -with: - -- **Skills** - explicit capabilities the AI can use. -- **Files** - documents, project assets, examples, and working material. -- **Memory** - durable, inspectable context that can evolve over time. -- **Scripts** - reliable repeatable actions, not just natural language intent. -- **Runtime** - an isolated environment where code, tools, and automation can run. - -## What This Is Not - -Fulling 3.0 should not become a generic platform dashboard. - -It is not primarily: - -- a prompt marketplace -- a DevOps control panel -- a generic chatbot builder -- a Kubernetes UI -- a menu of disconnected AI features - -Those capabilities may exist underneath, but the product experience should stay -focused on one job: **create and deliver a long-lived dedicated AI workspace**. - -## Current Foundation - -The current codebase is the v2.0 foundation for this direction. - -Fulling v2.0.0 is already released and provides: - -- project creation from scratch -- GitHub App based repository import -- isolated Kubernetes sandboxes -- browser terminal and file manager access -- live HTTPS runtime URLs -- optional PostgreSQL databases -- persisted background reconciliation for resources and project tasks -- global skill enablement and uninstall fan-out through `ProjectTask` - -These are the building blocks for 3.0. The next step is to reorganize the -product around dedicated AI workspaces rather than exposing the underlying -resource plane as the primary user experience. - -## Branch Strategy - -The repository now has clear long-lived lines: - -- `release/2.0` - v2 stabilization and patch work only -- `v3.0` - the active 3.0 product and architecture branch -- `main` - not the target branch for 3.0 work - -The v2 release is available at: - -- [Fulling v2.0.0 Release](https://github.com/FullAgent/fulling/releases/tag/v2.0.0) - -Generated repository documentation lives under: - -- [.qoder/repowiki](./.qoder/repowiki) - -## Architecture Direction - -The underlying system is a database-driven asynchronous control plane: +## How It Works ```text -User intent --> persisted desired state --> reconciliation jobs --> external execution --> durable workspace state --> user-visible progress +Create + -> choose who the workspace is for + -> describe the work it supports + -> add knowledge and files + -> configure skills, scripts, memory, and runtime + -> test the workspace + -> share it with the recipient + +Use + -> open the prepared workspace + -> ask for work through task-focused entry points + -> review outputs, files, and approvals + -> keep using the same workspace as context grows ``` -The v2 implementation already separates several important concerns: +## What Fulling Is Not -- **Control plane** - user actions, authentication, database state -- **Resource plane** - sandbox and database lifecycle -- **Task plane** - async project-level work through `ProjectTask` -- **Execution layer** - Kubernetes, ttyd, GitHub App, and sandbox-side commands +Fulling is not a prompt marketplace, generic chatbot builder, Kubernetes UI, or +DevOps control panel. Those pieces can exist behind the scenes, but the product +is organized around prepared AI workspaces. -For 3.0, these layers should move behind a simpler product model: +## Project Status -```text -Dedicated AI - -> profile - -> mission - -> knowledge - -> memory - -> skills - -> scripts - -> runtime -``` +Fulling v3.0 is the active product direction. The current repository is being +realigned around the workspace delivery model described above. -## Technology Stack +The previous v2.0.0 release is available at +[Fulling v2.0.0](https://github.com/FullAgent/fulling/releases/tag/v2.0.0). -### Frontend +Active branches: -- Next.js 16 App Router -- React 19 -- TypeScript -- Tailwind CSS v4 -- Shadcn/UI +- `v3.0` for the current product direction +- `release/2.0` for v2 maintenance +- `main` is not the target branch for v3 work -### Backend +## Tech Stack -- Node.js 22 -- Next.js API Routes and Server Actions -- Prisma -- NextAuth v5 +| Area | Stack | +| --- | --- | +| App | Next.js 16 App Router, React 19, TypeScript | +| UI | Tailwind CSS v4, Shadcn/UI, Radix UI | +| Data | Prisma, PostgreSQL | +| Auth | NextAuth v5 | +| Runtime | Kubernetes, KubeBlocks PostgreSQL, ttyd, FileBrowser | +| Integrations | GitHub App, sandbox-side commands, AI proxy | +| Testing | Vitest, ESLint | -### Infrastructure +## Repository Map -- Kubernetes -- PostgreSQL via KubeBlocks -- ttyd web terminal -- FileBrowser -- GitHub App integration +```text +app/ Next.js routes, dashboard, auth, API endpoints +components/ Shared UI and layout components +lib/ Application libraries and service code +prisma/ Database schema and migrations +runtime/ Runtime image and sandbox support files +public/ Static icons and assets +``` ## Local Development ### Prerequisites -- Node.js 22.12.0 or higher +- Node.js 22.12.0 or later - pnpm 10.20.0 - PostgreSQL database - Kubernetes cluster with KubeBlocks installed -- GitHub App and OAuth application credentials +- GitHub App and OAuth credentials for GitHub integration work ### Setup @@ -201,44 +154,16 @@ Open [http://localhost:3000](http://localhost:3000). ## Useful Commands ```bash -pnpm dev # Start development server -pnpm build # Build for production +pnpm dev # Start the development server +pnpm build # Generate Prisma client and build for production pnpm lint # Run ESLint -pnpm test # Run tests +pnpm test # Run Vitest +pnpm test:watch # Run Vitest in watch mode npx prisma generate # Generate Prisma client -npx prisma db push # Push schema to database +npx prisma db push # Push schema changes to the database ``` -## Repository Documentation - -This repository intentionally does not maintain a hand-written `docs/` tree on -the active v3 line. Generated repository documentation is kept under -[.qoder/repowiki](./.qoder/repowiki) and can be regenerated as the code changes. - -Durable release history lives in GitHub Releases, starting with -[Fulling v2.0.0](https://github.com/FullAgent/fulling/releases/tag/v2.0.0). - -## Security Model - -- Multi-provider authentication through NextAuth -- User-specific Kubernetes access through scoped kubeconfig -- User-specific namespaces for isolation -- HTTP Basic Auth protection for ttyd -- Secrets stored as project or user configuration -- Resource limits for sandbox workloads - -## License - -MIT License - see [LICENSE](LICENSE). - -## Acknowledgments - -- [Anthropic](https://www.anthropic.com/) for Claude Code -- [Sealos](https://sealos.io/) for Kubernetes platform -- [ttyd](https://github.com/tsl0922/ttyd) for web terminal -- [FileBrowser](https://github.com/filebrowser/filebrowser) for file management - -## Contact +## Documentation -- GitHub: [@fanux](https://github.com/fanux) -- Issues: [GitHub Issues](https://github.com/FullAgent/fulling/issues) +- [AGENTS.md](./AGENTS.md) - agent guidance for this repository +- [docs/architecture.md](./docs/architecture.md) - v3 system architecture diff --git a/design/DESIGN.md b/design/DESIGN.md deleted file mode 100644 index 53e421a..0000000 --- a/design/DESIGN.md +++ /dev/null @@ -1,289 +0,0 @@ -# Design System: Fulling v3 — Ethereal Glass - -## 1. Visual Theme & Atmosphere - -A deep-space, gallery-airy interface with confident asymmetric layouts and fluid spring-physics motion. The atmosphere is **clinical yet warm** — like a well-lit architecture studio floating in void. - -- **Density:** 4/10 — Art Gallery Airy. Generous whitespace, breathable layouts -- **Variance:** 7/10 — Offset Asymmetric. Split-screen heroes, staggered grids -- **Motion:** 6/10 — Fluid CSS. Spring-physics transitions, perpetual micro-loops - -The design philosophy centers on **"Double Bezel" (Doppelrand)** architecture — nested containers that create a sense of depth through an outer shell and an inner core. Combined with true glass morphism (inner borders + inner shadows for edge refraction), the interface feels like a layered holographic surface suspended in deep space. - -## 2. Color Palette & Roles - -### Core Neutrals -- **Void Black** (#050505) — Deepest background, the canvas of deep space -- **Abyss** (#0a0a0a) — Secondary background, terminal panels -- **Surface** (#0f0f0f) — Card fills, elevated containers -- **Surface Raised** (#141414) — Hover states, active panels -- **Ink** (#e8e8e8) — Primary text, maximum contrast on dark -- **Ink Dim** (#737373) — Secondary text, descriptions, metadata -- **Ink Faint** (#404040) — Tertiary text, disabled states, borders - -### Structural Borders -- **Border Default** (rgba(255,255,255,0.06)) — 1px structural lines, card outlines -- **Border Hover** (rgba(255,255,255,0.12)) — Elevated border on interaction - -### Accent Colors -- **Teal Accent** (#14b8a6) — Primary accent for CTAs, active states, focus rings, success indicators -- **Teal Glow** (#2dd4bf) — Hover state for teal elements, lighter variant -- **Teal Dim** (#0d9488) — Pressed/active state for teal elements -- **Amber Warm** (#f59e0b) — Secondary accent for warnings, highlights, warm metadata -- **Amber Glow** (#fbbf24) — Hover state for amber elements -- **Amber Dim** (#d97706) — Pressed/active state for amber elements - -### Background Effects -- Deep space gradient with teal/amber ambient orbs: - - Radial gradient at 15% 30%: rgba(20,184,166,0.08) — teal ambient glow - - Radial gradient at 85% 70%: rgba(245,158,11,0.05) — amber ambient glow - - Radial gradient at center: rgba(20,184,166,0.03) — subtle central haze - -### Color Rules -- Maximum 2 accent colors (teal primary, amber secondary). Both saturation below 80% -- The "AI Purple/Blue Neon" aesthetic is strictly BANNED -- Never use pure black (#000000) — Void Black (#050505) is the darkest allowed -- No neon/outer glow shadows on text or containers - -## 3. Typography Rules - -### Font Stack -- **Display / Headlines:** Geist — Track-tight, controlled scale, weight-driven hierarchy -- **Body:** Geist — Relaxed leading, max 65 characters per line -- **Mono:** Geist Mono — For code, metadata, timestamps, terminal output, status indicators - -### Type Scale -- Hero headlines: 5xl–7xl (48px–72px), font-bold, tracking-tight, leading-[1.05] -- Section titles: 2xl (24px), font-bold, tracking-tight -- Card titles: text-[15px], font-semibold -- Body text: text-lg (18px) / text-sm (14px), leading-relaxed -- Metadata / timestamps: text-[11px], font-mono -- Eyebrow tags: text-[10px], uppercase, letter-spacing 0.2em, font-weight 500 - -### Typography Patterns -- Headlines use weight and color contrast for hierarchy, not just massive size -- Gradient text (teal → amber) allowed only on hero headlines, never on body -- Glow text effects: teal glow (0 0 40px rgba(20,184,166,0.25)) for hero emphasis -- Mono font mandatory for all code blocks, terminal output, and numerical metadata - -### Banned -- Inter font (too generic for premium contexts) -- Generic serif fonts (Times New Roman, Georgia, Garamond) -- Serif fonts in dashboard or software UIs - -## 4. Component Stylings - -### Buttons -- **Primary:** Teal fill (#14b8a6), dark text (#050505), rounded-full, font-semibold - - Hover: Teal Glow (#2dd4bf), enhanced shadow - - Active: scale(0.98) tactile push feedback - - Shadow: 0 0 30px rgba(20,184,166,0.2) — subtle ambient glow, not neon -- **Secondary / Ghost:** Glass morphism (rgba(255,255,255,0.02) + blur), 1px border, rounded-full - - Hover: Background lightens to rgba(255,255,255,0.035), border shifts to teal/15 -- **Icon buttons:** 44px minimum touch target, rounded-full, glass morphism base - -### Cards -- **Glass Card:** background: rgba(255,255,255,0.02), backdrop-filter: blur(24px) saturate(1.2), border: 1px solid rgba(255,255,255,0.06), box-shadow: 0 8px 32px rgba(0,0,0,0.4) + inset 0 1px 0 rgba(255,255,255,0.04) - - Hover: Background lightens, border shifts to teal/15, shadow adds teal tint -- **Double Bezel Card:** Outer shell (padding: 2px, background: rgba(255,255,255,0.03), border: 1px solid rgba(255,255,255,0.06), border-radius: 1.5rem) + Inner core (background: rgba(10,10,10,0.8), border-radius: calc(1.5rem - 2px), box-shadow: inset 0 1px 1px rgba(255,255,255,0.06)) - - Used for: Stats counters, feature highlights, terminal containers -- **Spotlight Card:** Glass card with cursor-tracking radial gradient border - - Pseudo-element with radial-gradient(400px circle at mouse position, rgba(20,184,166,0.15), transparent 50%) - - Opacity transitions on hover (0 → 1 over 0.4s) - -### Navigation -- **Floating Nav Pill:** Glass morphism, rounded-full, px-2 py-2, centered fixed top - - Logo: Teal-accented icon (w-7 h-7, rounded-lg, bg-accent/15, border border-accent/20) + "Fulling" text - - Links: px-4 py-2, rounded-full, text-ink-dim, hover: text-ink + bg-white/5 - - CTA: Teal ghost button inside pill -- **Sidebar:** Glass panel, w-60, border-r border-white/5 - - Section headers: eyebrow style (text-[10px], uppercase, letter-spacing 0.2em) - - Active item: bg-accent/8, border border-accent/15, text-accent - - Inactive item: text-ink-dim, hover: text-ink + bg-white/5 - -### Terminal / Code Blocks -- Background: Abyss (#0a0a0a) -- Window chrome: Three dots (amber/teal/faint), mono font label -- Prompt: $ prefix in ink-dim, command in ink -- Output: Teal diamond (◆) for info, amber diamond for warnings -- Success banner: bg-accent/5, border border-accent/10, rounded-xl -- Cursor: Blinking underscore - -### Inputs / Forms -- Label above input, helper text optional, error text below -- Focus ring in teal accent color -- No floating labels -- Glass morphism background for input fields - -### Loading States -- Skeletal shimmer matching exact layout dimensions -- No generic circular spinners -- Pulse animation for status indicators (animate-pulse) - -### Empty States -- Composed compositions indicating how to populate data -- Not just "No data" text - -## 5. Layout Principles - -### Grid System -- CSS Grid over Flexbox math — never use calc() percentage hacks -- Contain layouts using max-width constraints (max-w-6xl for landing, max-w-5xl for dashboard) -- Generous internal padding (p-8 for dashboard content) - -### Hero Section -- **Asymmetric Split:** Left-aligned content (60%) + Right floating visual (40%) -- Left: Eyebrow tag → Headline → Subhead → CTA buttons → Stats row -- Right: Floating terminal with double bezel + floating status badges -- Centered Hero layouts are BANNED — always use split or left-aligned - -### Feature Rows -- The generic "3 equal cards horizontally" is BANNED -- Use: 2-column staggered grid, asymmetric splits, or horizontal scroll -- Cards should have varied heights and visual weights - -### Dashboard Layout -- Top bar: Glass header, logo left, status + avatar right -- Sidebar: Glass panel, collapsible sections with eyebrow headers -- Main content: Scrollable area with max-width containment -- Filter tabs: Glass pill container with segmented buttons - -### Spacing Philosophy -- Section gaps: clamp(3rem, 8vw, 6rem) -- Card internal padding: p-5 (20px) -- Element gaps: gap-4 (16px) standard, gap-3 (12px) for dense areas -- Border radius: rounded-2xl (1rem) for cards, rounded-full for buttons/pills - -### Responsive Rules -- Mobile-First Collapse (< 768px): All multi-column layouts collapse to single column -- No horizontal scroll on mobile — critical failure if present -- Headlines scale via clamp() or responsive prefixes (text-5xl lg:text-7xl) -- Body text minimum 1rem / 14px -- Touch targets: 44px minimum for all interactive elements -- Navigation: Desktop horizontal nav collapses to clean mobile menu - -## 6. Motion & Interaction - -### Animation Engine -- **Spring Physics default:** cubic-bezier(0.32, 0.72, 0, 1) for UI transitions -- **Reveal animation:** opacity 0 → 1, translateY(20px) → 0, blur(4px) → 0, duration 0.8s, easing cubic-bezier(0.16, 1, 0.3, 1) -- **Staggered cascade:** reveal-d1 (0.1s delay), reveal-d2 (0.2s), reveal-d3 (0.3s), reveal-d4 (0.4s) - -### Perpetual Micro-Interactions -- **Float animation:** translateY(0) → translateY(-8px) rotate(0.5deg), 7s ease-in-out infinite - - Applied to: Terminal container, floating badges -- **Pulse glow:** box-shadow oscillates between rgba(20,184,166,0.15) and rgba(20,184,166,0.3), 3s ease-in-out infinite - - Applied to: Status indicators, active elements -- **Cursor tracking:** Spotlight border follows mouse position within card bounds - -### Interaction States -- Hover: -translate-y-0.5 lift, border color shift, background lighten -- Active: scale(0.98) tactile push -- Focus: Teal accent ring - -### Performance Rules -- Animate exclusively via transform and opacity -- Never animate top, left, width, height -- Grain/noise filters on fixed pseudo-elements only (isolated from scroll) -- Backdrop-filter blur used sparingly — only on glass cards and nav - -## 7. Anti-Patterns (Banned) - -### Visual Bans -- No emojis anywhere in the interface -- No pure black (#000000) — use Void Black (#050505) -- No neon/outer glow shadows — only subtle ambient glow -- No oversaturated accents — keep saturation below 80% -- No excessive gradient text on large headers — only hero headline -- No custom mouse cursors - -### Layout Bans -- No overlapping elements — every element occupies its own clear spatial zone -- No centered Hero sections (for this high-variance project) -- No 3-column equal card layouts — use asymmetric grids -- No flexbox percentage math — use CSS Grid -- No h-screen — use min-h-[100dvh] for full-height sections - -### Typography Bans -- No Inter font -- No generic serif fonts (Times New Roman, Georgia, Garamond, Palatino) -- No serif fonts in dashboard or software UIs - -### Content Bans -- No generic names ("John Doe", "Acme", "Nexus") -- No fake round numbers ("99.99%", "50%") -- No AI copywriting clichés ("Elevate", "Seamless", "Unleash", "Next-Gen") -- No filler UI text: "Scroll to explore", "Swipe down", scroll arrows, bouncing chevrons -- No broken Unsplash links — use picsum.photos or SVG avatars - -## 8. File Structure & Implementation Notes - -### Key Files -- `design/style-ethereal.html` — Reference implementation (landing + dashboard) -- Tailwind CSS v4 via CDN for rapid prototyping -- Geist + Geist Mono via Google Fonts CDN - -### Tailwind Config Extensions -```javascript -colors: { - void: '#050505', - abyss: '#0a0a0a', - surface: '#0f0f0f', - 'surface-raised': '#141414', - ink: '#e8e8e8', - 'ink-dim': '#737373', - 'ink-faint': '#404040', - accent: '#14b8a6', - 'accent-glow': '#2dd4bf', - 'accent-dim': '#0d9488', - warm: '#f59e0b', - 'warm-glow': '#fbbf24', - 'warm-dim': '#d97706', - border: 'rgba(255,255,255,0.06)', - 'border-hover': 'rgba(255,255,255,0.12)', -}, -fontFamily: { - sans: ['"Geist"', 'sans-serif'], - mono: ['"Geist Mono"', 'monospace'], -} -``` - -### CSS Custom Properties for Spotlight -```css -.spotlight::before { - background: radial-gradient( - 400px circle at var(--mouse-x, 50%) var(--mouse-y, 50%), - rgba(20,184,166,0.15), - transparent 50% - ); -} -``` - -### JavaScript for Cursor Tracking -```javascript -document.addEventListener('mousemove', (e) => { - document.querySelectorAll('.spotlight').forEach(card => { - const rect = card.getBoundingClientRect(); - card.style.setProperty('--mouse-x', `${e.clientX - rect.left}px`); - card.style.setProperty('--mouse-y', `${e.clientY - rect.top}px`); - }); -}); -``` - -## 9. Design Tokens Summary - -| Token | Value | Usage | -|-------|-------|-------| -| Background | #050505 | Page canvas | -| Card Fill | rgba(255,255,255,0.02) | Glass card background | -| Card Border | rgba(255,255,255,0.06) | 1px structural border | -| Primary Text | #e8e8e8 | Headlines, body | -| Secondary Text | #737373 | Descriptions, metadata | -| Accent | #14b8a6 | CTAs, active states, focus | -| Secondary Accent | #f59e0b | Warnings, warm highlights | -| Font Sans | Geist | All UI text | -| Font Mono | Geist Mono | Code, terminal, numbers | -| Border Radius (Cards) | 1rem (rounded-2xl) | Containers | -| Border Radius (Pills) | 9999px (rounded-full) | Buttons, nav | -| Shadow | 0 8px 32px rgba(0,0,0,0.4) | Card elevation | -| Transition | 0.5s cubic-bezier(0.32, 0.72, 0, 1) | UI interactions | diff --git a/design/style-ethereal.html b/design/style-ethereal.html deleted file mode 100644 index 2f75384..0000000 --- a/design/style-ethereal.html +++ /dev/null @@ -1,1188 +0,0 @@ - - - - - - Fulling Design System Preview - - - - - - -
- - -
-
-
Persistent AI Workspace
-

- Turn every working relationship into a - durable environment. -

-

- Fulling organizes skills, files, memory, scripts, and runtime into one calm operating surface. - The result feels less like a chat window and more like a place where work can keep living. -

-
- - -
-
-
-
-
12m 47s
-
from blank profile to usable workspace
-
-
-
-
-
34
-
workspace files indexed for context
-
-
-
-
-
runtime: warm
-
scripts, terminal, and memory ready for the next session
-
-
-
-
- -
-
-
-
-
- - - -
- workspace/runtime - active -
-
- $ fulling workspace init iris - skills mounted: brief-writer, research-scan, deploy-check - memory attached: project preferences, tone notes, file map - runtime policy: ask before destructive shell operations - $ run prepare-session - scripts verified in isolated shell -
- Workspace ready. The environment can be resumed, edited, shared, or archived without rebuilding context. - -
-
-
-
-
- memory.lock - updated 18 minutes ago -
-
- skill graph - 7 capabilities available -
-
-
-
- -
-
-
-
Control Surface
-

The application view keeps the system quiet, inspectable, and ready for repeated use.

-
-

- This is the same visual language applied to an app surface: sidebar navigation, segmented status, - asymmetric panels, live memory, and a small script console. -

-
- -
-
- - -
-
-
-
Session 04
-

Workspace readiness

-
-
- - synced 2m ago -
-
- -
-
-
-
- Memory and working files - 34 files -
-
-
-
SK
-
- Research briefing skill -

Reads notes, drafts structured summaries, and keeps citations attached.

-
- skill -
-
-
MM
-
- Personal working memory -

Stores style preferences, project facts, and recurring review criteria.

-
- memory -
-
-
FS
-
- Workspace file map -

Tracks active drafts, inputs, scripts, and generated artifacts.

-
- files -
-
-
-
- -
-
-
- Script console - runtime -
-
- $ ./scripts/check-context - memory graph consistent - file permissions verified - pending approval: publish report - $ ./scripts/start-session -
-
-
-
- -
-
-
-
- Context load - active -
-
47.2%
-

Current session uses prior memory without saturating the window.

-
-
- -
-
-
- Recent reconciliation - queue -
-
-
10:34skill mounted
-
10:36files indexed
-
10:41runtime warmed
-
10:48memory saved
-
-
-
-
-
-
-
-
-
- - - - diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..bb6a41c --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,170 @@ +# Fulling v3 架构目录 + +## 1. 产品北极星 + +- 开箱即用的 AI workspace 交付产品 +- Creator 负责配置 +- Recipient 直接使用 +- 交付物是 workspace,不是 prompt、chatbot 或资源面板 + +## 2. 用户角色 + +- Creator +- Recipient +- Workspace Owner +- Workspace Member +- Admin + +## 3. 核心对象 + +- Workspace +- Workspace Template +- Workspace Instance +- Profile +- Mission +- Knowledge +- Memory +- Skill +- Script +- Runtime +- Share Link +- Access Grant + +## 4. Workspace 生命周期 + +- Create +- Configure +- Test +- Publish +- Share +- Use +- Update +- Archive + +## 5. Creator 侧模块 + +- Workspace Builder +- Mission Editor +- Knowledge Manager +- Skill Manager +- Script Manager +- Memory Manager +- Runtime Settings +- Test Console +- Share Settings + +## 6. Recipient 侧模块 + +- Workspace Home +- Task Entry +- Chat / Work Thread +- File Upload +- Output Review +- Approval Center +- Workspace History + +## 7. 系统分层 + +- Experience Layer +- Workspace Domain Layer +- Capability Layer +- Runtime Layer +- Persistence Layer +- Access Control Layer + +## 8. 数据模型目录 + +- User +- Workspace +- WorkspaceTemplate +- WorkspaceMember +- WorkspaceShare +- WorkspaceKnowledge +- WorkspaceMemory +- WorkspaceSkill +- WorkspaceScript +- WorkspaceRuntime +- WorkspaceThread +- WorkspaceRun +- WorkspaceArtifact + +## 9. 关键流程目录 + +- 创建 workspace +- 从 template 创建 workspace +- 配置 mission +- 导入 knowledge +- 安装 skill +- 添加 script +- 初始化 runtime +- 创建分享链接 +- Recipient 首次进入 +- Recipient 发起任务 +- AI 执行任务 +- 产出 artifact +- 更新 memory +- 归档 workspace + +## 10. 权限目录 + +- Creator 权限 +- Recipient 权限 +- Admin 权限 +- 分享链接权限 +- Workspace 成员权限 +- Runtime 操作权限 +- Knowledge 访问权限 +- Memory 修改权限 + +## 11. Runtime 目录 + +- Runtime Provisioning +- Runtime Status +- Runtime Files +- Runtime Secrets +- Runtime Tools +- Runtime Logs +- Runtime Reset +- Runtime Shutdown + +## 12. Artifact 目录 + +- Text Output +- File Output +- Report +- Table +- Image +- Script Result +- Runtime Log +- Approval Record + +## 13. 非目标 + +- Prompt marketplace +- Generic chatbot builder +- Kubernetes UI +- DevOps control panel +- 通用资源管理面板 + +## 14. MVP 切片 + +- Creator 创建 workspace +- Creator 配置 mission +- Creator 添加 knowledge +- Creator 绑定一个 skill 或 script +- Creator 测试 workspace +- Creator 分享 workspace +- Recipient 打开 workspace +- Recipient 完成一次任务 + +## 15. 后续待设计 + +- Template marketplace +- Billing +- Team workspace +- Enterprise permission model +- Workspace versioning +- Skill authoring +- Script authoring +- Runtime isolation +- Audit log From 12e76a794fdd0dd6ffc0e2da9d6c6400fe0f2a6c Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Tue, 23 Jun 2026 17:25:06 +0800 Subject: [PATCH 4/7] docs: define platform directory boundaries (#164) --- docs/architecture.md | 91 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 7 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index bb6a41c..c4886a8 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -88,7 +88,84 @@ - WorkspaceRun - WorkspaceArtifact -## 9. 关键流程目录 +## 9. 代码目录边界 + +### lib/ + +- 全应用基础设施 +- 通用工具 +- 环境变量 +- 数据库入口 +- Auth 入口 +- Logger +- JWT +- Fetch client + +### lib/platform/ + +- Fulling 平台核心 +- Control +- Orchestrators +- Executors +- Persistence +- Integrations +- Workspace 业务对象 +- Runtime 编排 +- Knowledge 管理 +- Memory 管理 +- Skill 绑定 +- Script 绑定 +- Access 控制 +- Artifact 管理 + +### lib/platform/control/ + +- 用户意图入口 +- Command +- Query +- 输入校验 +- 业务规则判断 +- 持久状态写入 +- 后续任务入队 + +不放: + +- Route handler +- React / UI +- 长时间任务执行 +- 外部 SDK 协议细节 +- Reconciliation loop + +### lib/platform/control/commands/ + +- create-workspace +- create-workspace-from-template +- configure-workspace-mission +- import-workspace-knowledge +- bind-workspace-skill +- add-workspace-script +- initialize-workspace-runtime +- create-workspace-share +- archive-workspace + +### lib/platform/integrations/ + +- 外部平台适配 +- 外部 SDK 封装 +- 外部协议通信 +- Provider-specific 逻辑 + +### lib/platform/integrations/sealos/ + +- auth +- session +- k8s +- devbox +- aiproxy +- namespace +- runtime adapter + +## 10. 关键流程目录 - 创建 workspace - 从 template 创建 workspace @@ -105,7 +182,7 @@ - 更新 memory - 归档 workspace -## 10. 权限目录 +## 11. 权限目录 - Creator 权限 - Recipient 权限 @@ -116,7 +193,7 @@ - Knowledge 访问权限 - Memory 修改权限 -## 11. Runtime 目录 +## 12. Runtime 目录 - Runtime Provisioning - Runtime Status @@ -127,7 +204,7 @@ - Runtime Reset - Runtime Shutdown -## 12. Artifact 目录 +## 13. Artifact 目录 - Text Output - File Output @@ -138,7 +215,7 @@ - Runtime Log - Approval Record -## 13. 非目标 +## 14. 非目标 - Prompt marketplace - Generic chatbot builder @@ -146,7 +223,7 @@ - DevOps control panel - 通用资源管理面板 -## 14. MVP 切片 +## 15. MVP 切片 - Creator 创建 workspace - Creator 配置 mission @@ -157,7 +234,7 @@ - Recipient 打开 workspace - Recipient 完成一次任务 -## 15. 后续待设计 +## 16. 后续待设计 - Template marketplace - Billing From 465ddb31fb1fdc7f6fccb78ff85099dec08f19c9 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Wed, 24 Jun 2026 14:58:35 +0800 Subject: [PATCH 5/7] [codex] move Sealos auth into platform integration (#165) * refactor: add sealos auth integration types * docs: add sealos auth integration plan * refactor: extract sealos iframe detection * fix: harden sealos iframe detection * refactor: extract sealos session loading * fix: clean up failed sealos session loads * refactor: export sealos auth integration * refactor: use sealos auth integration in provider * fix: clean up pending sealos provider sessions * fix: support strict mode sealos provider replay * test: cover sealos provider lifecycle --- .../sealos/auth/detect-sealos-iframe.test.ts | 95 ++++++ .../sealos/auth/detect-sealos-iframe.ts | 38 +++ .../sealos/auth/get-sealos-session.test.ts | 104 ++++++ .../sealos/auth/get-sealos-session.ts | 41 +++ .../integrations/sealos/auth/index.ts | 3 + .../integrations/sealos/auth/types.ts | 15 + package.json | 1 + pnpm-lock.yaml | 315 +++++++++++++++++- provider/sealos.test.tsx | 185 ++++++++++ provider/sealos.tsx | 67 ++-- vitest.config.ts | 2 +- 11 files changed, 806 insertions(+), 60 deletions(-) create mode 100644 lib/platform/integrations/sealos/auth/detect-sealos-iframe.test.ts create mode 100644 lib/platform/integrations/sealos/auth/detect-sealos-iframe.ts create mode 100644 lib/platform/integrations/sealos/auth/get-sealos-session.test.ts create mode 100644 lib/platform/integrations/sealos/auth/get-sealos-session.ts create mode 100644 lib/platform/integrations/sealos/auth/index.ts create mode 100644 lib/platform/integrations/sealos/auth/types.ts create mode 100644 provider/sealos.test.tsx diff --git a/lib/platform/integrations/sealos/auth/detect-sealos-iframe.test.ts b/lib/platform/integrations/sealos/auth/detect-sealos-iframe.test.ts new file mode 100644 index 0000000..2003d51 --- /dev/null +++ b/lib/platform/integrations/sealos/auth/detect-sealos-iframe.test.ts @@ -0,0 +1,95 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' + +import { detectSealosIframe } from '@/lib/platform/integrations/sealos/auth/detect-sealos-iframe' + +describe('detectSealosIframe', () => { + afterEach(() => { + vi.unstubAllGlobals() + }) + + it('returns false when no window is provided', () => { + expect(detectSealosIframe(undefined)).toBe(false) + }) + + it('returns false when the window has no ancestor origin', () => { + expect(detectSealosIframe({ location: {} })).toBe(false) + }) + + it('returns true for sealos.io ancestor origins', () => { + expect( + detectSealosIframe({ + location: { + ancestorOrigins: ['https://cloud.sealos.io'], + }, + }), + ).toBe(true) + }) + + it('returns true for sealos.run ancestor origins', () => { + expect( + detectSealosIframe({ + location: { + ancestorOrigins: ['https://workspace.sealos.run'], + }, + }), + ).toBe(true) + }) + + it('returns false for non-Sealos ancestor origins', () => { + expect( + detectSealosIframe({ + location: { + ancestorOrigins: ['https://example.com'], + }, + }), + ).toBe(false) + }) + + it('returns false for spoofed Sealos-like ancestor hosts', () => { + expect( + detectSealosIframe({ + location: { + ancestorOrigins: ['https://sealos.io.evil.com'], + }, + }), + ).toBe(false) + + expect( + detectSealosIframe({ + location: { + ancestorOrigins: ['https://evil-sealos.run.example'], + }, + }), + ).toBe(false) + + expect( + detectSealosIframe({ + location: { + ancestorOrigins: ['https://example.com?next=sealos.io'], + }, + }), + ).toBe(false) + }) + + it('returns false for malformed ancestor origin strings', () => { + expect( + detectSealosIframe({ + location: { + ancestorOrigins: ['not a url with sealos.io'], + }, + }), + ).toBe(false) + }) + + it('returns false when ancestor origin access throws', () => { + const browserWindow = { + location: { + get ancestorOrigins() { + throw new Error('blocked') + }, + }, + } + + expect(detectSealosIframe(browserWindow)).toBe(false) + }) +}) diff --git a/lib/platform/integrations/sealos/auth/detect-sealos-iframe.ts b/lib/platform/integrations/sealos/auth/detect-sealos-iframe.ts new file mode 100644 index 0000000..427ef5f --- /dev/null +++ b/lib/platform/integrations/sealos/auth/detect-sealos-iframe.ts @@ -0,0 +1,38 @@ +type BrowserWindowLike = { + location?: { + ancestorOrigins?: ArrayLike + } +} + +/** + * Detects whether the current browser frame is embedded by Sealos. + * + * Expected inputs: + * - A browser `window` object, or no value in server/test environments. + * + * Expected outputs: + * - Returns true when the first ancestor origin is a known Sealos host. + * + * Out of scope: + * - Does not initialize the Sealos SDK. + * - Does not authenticate the Fulling user. + */ +export function detectSealosIframe(browserWindow?: BrowserWindowLike): boolean { + if (!browserWindow) return false + + try { + const ancestorOrigin = browserWindow.location?.ancestorOrigins?.[0] + if (!ancestorOrigin) return false + + const hostname = new URL(ancestorOrigin).hostname + + return ( + hostname === 'sealos.io' || + hostname.endsWith('.sealos.io') || + hostname === 'sealos.run' || + hostname.endsWith('.sealos.run') + ) + } catch { + return false + } +} diff --git a/lib/platform/integrations/sealos/auth/get-sealos-session.test.ts b/lib/platform/integrations/sealos/auth/get-sealos-session.test.ts new file mode 100644 index 0000000..8dfb88b --- /dev/null +++ b/lib/platform/integrations/sealos/auth/get-sealos-session.test.ts @@ -0,0 +1,104 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { createSealosApp, sealosApp } = vi.hoisted(() => ({ + createSealosApp: vi.fn(), + sealosApp: { + getSession: vi.fn(), + }, +})) + +vi.mock('@zjy365/sealos-desktop-sdk/app', () => ({ + createSealosApp, + sealosApp, +})) + +import { getSealosSession } from '@/lib/platform/integrations/sealos/auth/get-sealos-session' + +describe('getSealosSession', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('initializes the SDK and returns a normalized Sealos auth session', async () => { + const cleanup = vi.fn() + createSealosApp.mockReturnValue(cleanup) + sealosApp.getSession.mockResolvedValue({ + token: 'sealos-token', + kubeconfig: 'apiVersion: v1', + user: { + id: 'user-id', + name: 'sealos-user', + avatar: 'https://example.com/avatar.png', + k8sUsername: 'k8s-user', + nsid: 'ns-user', + }, + }) + + const result = await getSealosSession() + + expect(result).toEqual({ + token: 'sealos-token', + kubeconfig: 'apiVersion: v1', + namespaceId: 'ns-user', + user: { + id: 'user-id', + name: 'sealos-user', + avatar: 'https://example.com/avatar.png', + k8sUsername: 'k8s-user', + nsid: 'ns-user', + }, + cleanup, + }) + expect(createSealosApp).toHaveBeenCalledTimes(1) + expect(sealosApp.getSession).toHaveBeenCalledTimes(1) + }) + + it('uses a no-op cleanup when the SDK initializer returns nothing', async () => { + createSealosApp.mockReturnValue(undefined) + sealosApp.getSession.mockResolvedValue({ + token: 'sealos-token', + kubeconfig: 'apiVersion: v1', + user: { + id: 'user-id', + name: 'sealos-user', + avatar: '', + k8sUsername: 'k8s-user', + nsid: 'ns-user', + }, + }) + + const result = await getSealosSession() + + expect(() => result.cleanup()).not.toThrow() + }) + + it('rethrows session loading errors from the SDK', async () => { + const error = new Error('session unavailable') + const cleanup = vi.fn() + createSealosApp.mockReturnValue(cleanup) + sealosApp.getSession.mockRejectedValue(error) + + await expect(getSealosSession()).rejects.toThrow(error) + expect(cleanup).toHaveBeenCalledTimes(1) + }) + + it('cleans up and throws a clear error when the session token is missing', async () => { + const cleanup = vi.fn() + createSealosApp.mockReturnValue(cleanup) + sealosApp.getSession.mockResolvedValue({ + kubeconfig: 'apiVersion: v1', + user: { + id: 'user-id', + name: 'sealos-user', + avatar: '', + k8sUsername: 'k8s-user', + nsid: 'ns-user', + }, + }) + + await expect(getSealosSession()).rejects.toThrow( + 'Sealos session token is missing', + ) + expect(cleanup).toHaveBeenCalledTimes(1) + }) +}) diff --git a/lib/platform/integrations/sealos/auth/get-sealos-session.ts b/lib/platform/integrations/sealos/auth/get-sealos-session.ts new file mode 100644 index 0000000..ed7c7f8 --- /dev/null +++ b/lib/platform/integrations/sealos/auth/get-sealos-session.ts @@ -0,0 +1,41 @@ +import { createSealosApp, sealosApp } from '@zjy365/sealos-desktop-sdk/app' + +import type { SealosAuthSession } from './types' + +/** + * Loads the current Sealos Desktop auth session. + * + * Expected inputs: + * - Must run in a Sealos Desktop iframe after the caller has detected Sealos. + * + * Expected outputs: + * - Returns normalized token, kubeconfig, user, namespace, and cleanup values. + * + * Out of scope: + * - Does not decide whether the current browser is in Sealos. + * - Does not authenticate the Fulling NextAuth session. + */ +export async function getSealosSession(): Promise { + const cleanupApp = createSealosApp() + let sealosSession: Awaited> + + try { + sealosSession = await sealosApp.getSession() + } catch (error) { + cleanupApp?.() + throw error + } + + if (!sealosSession.token) { + cleanupApp?.() + throw new Error('Sealos session token is missing') + } + + return { + token: sealosSession.token, + kubeconfig: sealosSession.kubeconfig, + user: sealosSession.user, + namespaceId: sealosSession.user.nsid, + cleanup: cleanupApp ?? (() => {}), + } +} diff --git a/lib/platform/integrations/sealos/auth/index.ts b/lib/platform/integrations/sealos/auth/index.ts new file mode 100644 index 0000000..07cf6d1 --- /dev/null +++ b/lib/platform/integrations/sealos/auth/index.ts @@ -0,0 +1,3 @@ +export { detectSealosIframe } from './detect-sealos-iframe' +export { getSealosSession } from './get-sealos-session' +export type { SealosAuthSession, SealosUserInfo } from './types' diff --git a/lib/platform/integrations/sealos/auth/types.ts b/lib/platform/integrations/sealos/auth/types.ts new file mode 100644 index 0000000..e180f43 --- /dev/null +++ b/lib/platform/integrations/sealos/auth/types.ts @@ -0,0 +1,15 @@ +export interface SealosUserInfo { + id: string + name: string + avatar: string + k8sUsername: string + nsid: string +} + +export interface SealosAuthSession { + token: string + kubeconfig: string + user: SealosUserInfo + namespaceId: string + cleanup: () => void +} diff --git a/package.json b/package.json index c7d532f..aeb6042 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "eslint-config-next": "16.0.7", "eslint-config-prettier": "^10.1.8", "eslint-plugin-simple-import-sort": "^12.1.1", + "jsdom": "^29.1.1", "prettier": "^3.7.4", "tailwindcss": "^4.3.0", "tw-animate-css": "^1.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7bd56fa..9d75d23 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -189,6 +189,9 @@ importers: eslint-plugin-simple-import-sort: specifier: ^12.1.1 version: 12.1.1(eslint@9.39.2(jiti@2.6.1)) + jsdom: + specifier: ^29.1.1 + version: 29.1.1 prettier: specifier: ^3.7.4 version: 3.7.4 @@ -206,7 +209,7 @@ importers: version: 6.1.1(typescript@5.9.3)(vite@8.0.3(@emnapi/core@1.7.1)(@emnapi/runtime@1.7.1)(@types/node@20.19.27)(jiti@2.6.1)) vitest: specifier: ^4.1.2 - version: 4.1.2(@types/node@20.19.27)(vite@8.0.3(@emnapi/core@1.7.1)(@emnapi/runtime@1.7.1)(@types/node@20.19.27)(jiti@2.6.1)) + version: 4.1.2(@types/node@20.19.27)(jsdom@29.1.1)(vite@8.0.3(@emnapi/core@1.7.1)(@emnapi/runtime@1.7.1)(@types/node@20.19.27)(jiti@2.6.1)) packages: @@ -214,6 +217,21 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@7.1.1': + resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@auth/core@0.41.0': resolution: {integrity: sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ==} peerDependencies: @@ -295,6 +313,46 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.0.2': + resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.8': + resolution: {integrity: sha512-3chWb7PRLijpJpPIKkDxdu6IBeO5MrFACND57On0j8OPpc0wZibcGc3xAHrSEbOx/KDRyMHoIxGn0w1PhXMYHw==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.5': + resolution: {integrity: sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + '@emnapi/core@1.7.1': resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} @@ -342,6 +400,15 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@floating-ui/core@1.7.3': resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} @@ -2180,6 +2247,9 @@ packages: before-after-hook@4.0.0: resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -2288,6 +2358,10 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -2297,6 +2371,10 @@ packages: damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -2329,6 +2407,9 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -2396,6 +2477,10 @@ packages: resolution: {integrity: sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==} engines: {node: '>=10.13.0'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + es-abstract@1.24.1: resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} engines: {node: '>= 0.4'} @@ -2763,6 +2848,10 @@ packages: resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} engines: {node: '>=14'} + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2854,6 +2943,9 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -2930,6 +3022,15 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsdom@29.1.1: + resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsep@1.4.0: resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} engines: {node: '>= 10.16.0'} @@ -3119,6 +3220,10 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -3134,6 +3239,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -3315,6 +3423,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -3372,10 +3483,6 @@ packages: resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.8: - resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} - engines: {node: ^10 || ^12 || >=14} - preact-render-to-string@6.5.11: resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==} peerDependencies: @@ -3513,6 +3620,10 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} @@ -3570,6 +3681,10 @@ packages: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -3729,6 +3844,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tailwind-merge@3.4.0: resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} @@ -3766,6 +3884,13 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tldts-core@7.4.4: + resolution: {integrity: sha512-vwVLJVvvpslm7vqAH7+XNj/neA/Ynq7DT2EEcMuwc5YzN5XaMyRAqxwU+uX3azZ1FQtB2gvrvnLnAEkvYlVdfg==} + + tldts@7.4.4: + resolution: {integrity: sha512-kFXFK7O4WPextIUAOk8qtnw9dxR9UIXP9CjuH1cTBVBZMDeQcUPgr/IazGiw1B0Yiw5L75gHLWeW4iD793r90g==} + hasBin: true + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3774,6 +3899,10 @@ packages: resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} engines: {node: '>=12'} + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + tr46@4.1.1: resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} engines: {node: '>=14'} @@ -3849,6 +3978,10 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici@7.28.0: + resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} + engines: {node: '>=20.18.1'} + universal-github-app-jwt@2.2.2: resolution: {integrity: sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==} @@ -3982,10 +4115,22 @@ packages: jsdom: optional: true + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + whatwg-url@13.0.0: resolution: {integrity: sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==} engines: {node: '>=16'} @@ -4035,6 +4180,13 @@ packages: utf-8-validate: optional: true + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -4055,6 +4207,26 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.8(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@asamuzakjp/dom-selector@7.1.1': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + + '@asamuzakjp/generational-cache@1.0.1': {} + + '@asamuzakjp/nwsapi@2.3.9': {} + '@auth/core@0.41.0': dependencies: '@panva/hkdf': 1.2.1 @@ -4163,6 +4335,34 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.0.2': {} + + '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.8(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.0.2 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.5(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + '@emnapi/core@1.7.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -4225,6 +4425,8 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@exodus/bytes@1.15.1': {} + '@floating-ui/core@1.7.3': dependencies: '@floating-ui/utils': 0.2.10 @@ -6046,6 +6248,10 @@ snapshots: before-after-hook@4.0.0: {} + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -6161,12 +6367,22 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + csstype@3.2.3: {} custom-error-instance@2.1.1: {} damerau-levenshtein@1.0.8: {} + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 13.0.0 + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -6195,6 +6411,8 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js@10.6.0: {} + deep-is@0.1.4: {} deepmerge-ts@7.1.5: {} @@ -6257,6 +6475,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.3 + entities@8.0.0: {} + es-abstract@1.24.1: dependencies: array-buffer-byte-length: 1.0.2 @@ -6765,6 +6985,12 @@ snapshots: hpagent@1.2.0: {} + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.1 + transitivePeerDependencies: + - '@noble/hashes' + ignore@5.3.2: {} ignore@7.0.5: {} @@ -6857,6 +7083,8 @@ snapshots: is-number@7.0.0: {} + is-potential-custom-element-name@1.0.1: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -6929,6 +7157,32 @@ snapshots: dependencies: argparse: 2.0.1 + jsdom@29.1.1: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@asamuzakjp/dom-selector': 7.1.1 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.5(css-tree@3.2.1) + '@exodus/bytes': 1.15.1 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.1 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.1 + undici: 7.28.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 13.0.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + jsep@1.4.0: {} jsesc@3.1.0: {} @@ -7098,6 +7352,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + lru-cache@11.5.1: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -7112,6 +7368,8 @@ snapshots: math-intrinsics@1.1.0: {} + mdn-data@2.27.1: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -7281,6 +7539,10 @@ snapshots: dependencies: callsites: 3.1.0 + parse5@8.0.1: + dependencies: + entities: 8.0.0 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -7339,12 +7601,6 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.8: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - preact-render-to-string@6.5.11(preact@10.24.3): dependencies: preact: 10.24.3 @@ -7527,6 +7783,8 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + require-from-string@2.0.2: {} + requires-port@1.0.0: {} resolve-from@4.0.0: {} @@ -7602,6 +7860,10 @@ snapshots: safe-stable-stringify@2.5.0: {} + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.27.0: {} semver@6.3.1: {} @@ -7817,6 +8079,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + symbol-tree@3.2.4: {} + tailwind-merge@3.4.0: {} tailwindcss@4.3.0: {} @@ -7865,12 +8129,22 @@ snapshots: tinyrainbow@3.1.0: {} + tldts-core@7.4.4: {} + + tldts@7.4.4: + dependencies: + tldts-core: 7.4.4 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 toad-cache@3.7.0: {} + tough-cookie@6.0.1: + dependencies: + tldts: 7.4.4 + tr46@4.1.1: dependencies: punycode: 2.3.1 @@ -7965,6 +8239,8 @@ snapshots: undici-types@7.16.0: {} + undici@7.28.0: {} + universal-github-app-jwt@2.2.2: {} universal-user-agent@7.0.3: {} @@ -8043,7 +8319,7 @@ snapshots: dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.8 + postcss: 8.5.14 rolldown: 1.0.0-rc.12(@emnapi/core@1.7.1)(@emnapi/runtime@1.7.1) tinyglobby: 0.2.15 optionalDependencies: @@ -8054,7 +8330,7 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' - vitest@4.1.2(@types/node@20.19.27)(vite@8.0.3(@emnapi/core@1.7.1)(@emnapi/runtime@1.7.1)(@types/node@20.19.27)(jiti@2.6.1)): + vitest@4.1.2(@types/node@20.19.27)(jsdom@29.1.1)(vite@8.0.3(@emnapi/core@1.7.1)(@emnapi/runtime@1.7.1)(@types/node@20.19.27)(jiti@2.6.1)): dependencies: '@vitest/expect': 4.1.2 '@vitest/mocker': 4.1.2(vite@8.0.3(@emnapi/core@1.7.1)(@emnapi/runtime@1.7.1)(@types/node@20.19.27)(jiti@2.6.1)) @@ -8078,11 +8354,20 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.19.27 + jsdom: 29.1.1 transitivePeerDependencies: - msw + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + webidl-conversions@7.0.0: {} + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + whatwg-url@13.0.0: dependencies: tr46: 4.1.1 @@ -8144,6 +8429,10 @@ snapshots: ws@8.18.3: {} + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + yallist@3.1.1: {} yocto-queue@0.1.0: {} diff --git a/provider/sealos.test.tsx b/provider/sealos.test.tsx new file mode 100644 index 0000000..0bd7b35 --- /dev/null +++ b/provider/sealos.test.tsx @@ -0,0 +1,185 @@ +/** + * @vitest-environment jsdom + */ + +import { act } from 'react' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +const { detectSealosIframe, getSealosSession } = vi.hoisted(() => ({ + detectSealosIframe: vi.fn(), + getSealosSession: vi.fn(), +})) + +vi.mock('@/lib/platform/integrations/sealos/auth', () => ({ + detectSealosIframe, + getSealosSession, +})) + +import { SealosProvider, useSealos } from '@/provider/sealos' + +function Deferred() { + let resolve!: (value: T) => void + let reject!: (error: unknown) => void + const promise = new Promise((promiseResolve, promiseReject) => { + resolve = promiseResolve + reject = promiseReject + }) + + return { promise, resolve, reject } +} + +function createSession(input: { + token: string + namespaceId: string + cleanup: () => void +}) { + return { + token: input.token, + kubeconfig: 'apiVersion: v1', + user: { + id: 'user-id', + name: 'sealos-user', + avatar: '', + k8sUsername: 'k8s-user', + nsid: input.namespaceId, + }, + namespaceId: input.namespaceId, + cleanup: input.cleanup, + } +} + +function SealosStateView() { + const sealos = useSealos() + + return ( + + {JSON.stringify({ + isInitialized: sealos.isInitialized, + isLoading: sealos.isLoading, + isSealos: sealos.isSealos, + sealosToken: sealos.sealosToken, + sealosNs: sealos.sealosNs, + })} + + ) +} + +describe('SealosProvider', () => { + let container: HTMLDivElement + let root: Root + + beforeEach(() => { + vi.clearAllMocks() + detectSealosIframe.mockReturnValue(true) + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) + }) + + afterEach(() => { + act(() => { + root.unmount() + }) + container.remove() + }) + + it('cleans up stale sessions and still initializes after Strict Mode effect replay', async () => { + const firstSession = Deferred>() + const secondSession = Deferred>() + const firstCleanup = vi.fn() + const secondCleanup = vi.fn() + + getSealosSession + .mockReturnValueOnce(firstSession.promise) + .mockReturnValueOnce(secondSession.promise) + + act(() => { + root.render( + + + , + ) + }) + + act(() => { + root.unmount() + }) + + root = createRoot(container) + act(() => { + root.render( + + + , + ) + }) + + await act(async () => { + firstSession.resolve( + createSession({ + token: 'stale-token', + namespaceId: 'stale-ns', + cleanup: firstCleanup, + }), + ) + }) + + expect(firstCleanup).toHaveBeenCalledTimes(1) + + await act(async () => { + secondSession.resolve( + createSession({ + token: 'active-token', + namespaceId: 'active-ns', + cleanup: secondCleanup, + }), + ) + }) + + expect(getSealosSession).toHaveBeenCalledTimes(2) + expect(secondCleanup).not.toHaveBeenCalled() + + const state = JSON.parse( + container.querySelector('[data-testid="sealos-state"]')?.textContent ?? '{}', + ) + + expect(state).toEqual({ + isInitialized: true, + isLoading: false, + isSealos: true, + sealosToken: 'active-token', + sealosNs: 'active-ns', + }) + + act(() => { + root.unmount() + }) + + expect(secondCleanup).toHaveBeenCalledTimes(1) + }) + + it('does not set fallback state after unmount when session loading fails', async () => { + const pendingSession = Deferred>() + getSealosSession.mockReturnValueOnce(pendingSession.promise) + + act(() => { + root.render( + + + , + ) + }) + + act(() => { + root.unmount() + }) + + await act(async () => { + pendingSession.reject(new Error('session unavailable')) + }) + + expect(getSealosSession).toHaveBeenCalledTimes(1) + expect(container.textContent).toBe('') + }) +}) diff --git a/provider/sealos.tsx b/provider/sealos.tsx index 21c2b2a..e6807b4 100644 --- a/provider/sealos.tsx +++ b/provider/sealos.tsx @@ -1,35 +1,14 @@ 'use client'; import React, { createContext, useContext, useEffect, useRef, useState } from 'react'; -import { createSealosApp, sealosApp } from '@zjy365/sealos-desktop-sdk/app'; - -interface SealosUserInfo { - id: string; - name: string; - avatar: string; - k8sUsername: string; - nsid: string; -} -let sealosInitPromise: Promise | null = null; +import { + detectSealosIframe, + getSealosSession, + type SealosUserInfo, +} from '@/lib/platform/integrations/sealos/auth'; -/** - * Detect if running inside Sealos iframe environment - * Uses ancestorOrigins to check parent frame domain - */ -function isSealosIframe(): boolean { - if (typeof window === 'undefined') return false; - - try { - const ancestorOrigin = window.location.ancestorOrigins?.[0]; - if (!ancestorOrigin) return false; - - // Check if ancestor domain contains Sealos domains - return ancestorOrigin.includes('sealos.io') || ancestorOrigin.includes('sealos.run'); - } catch { - return false; - } -} +let sealosInitPromise: Promise | null = null; interface SealosContextType { isInitialized: boolean; @@ -56,20 +35,16 @@ export function SealosProvider({ children }: { children: React.ReactNode }) { sealosNs: null, }); - const initializationRef = useRef(false); const cleanupRef = useRef<(() => void) | null>(null); useEffect(() => { - // prevent multiple initialization - if (initializationRef.current) return; - initializationRef.current = true; + let disposed = false; const initializeSealos = async () => { try { setState((prev) => ({ ...prev, isLoading: true, error: null })); - // First, check if we're in Sealos iframe environment - const isInSealosIframe = isSealosIframe(); + const isInSealosIframe = detectSealosIframe(window); if (!isInSealosIframe) { // Not in Sealos environment, skip SDK initialization @@ -87,36 +62,34 @@ export function SealosProvider({ children }: { children: React.ReactNode }) { return; } - // In Sealos iframe, initialize SDK and get credentials console.info('Detected Sealos iframe environment, initializing SDK...'); - const cleanupApp = createSealosApp(); - - // get session info console.info('Getting Sealos session...'); - const sealosSession = await sealosApp.getSession(); - const sealosToken = sealosSession.token as unknown as string; - const sealosNs = sealosSession.user.nsid; + const sealosSession = await getSealosSession(); + + if (disposed) { + sealosSession.cleanup(); + return; + } setState({ isInitialized: true, isLoading: false, isSealos: true, error: null, - sealosToken, + sealosToken: sealosSession.token, sealosKubeconfig: sealosSession.kubeconfig, sealosUser: sealosSession.user, - sealosNs, + sealosNs: sealosSession.namespaceId, }); - // cleanup - cleanupRef.current = () => { - cleanupApp?.(); - }; + cleanupRef.current = sealosSession.cleanup; } catch (error) { console.info( 'Sealos SDK initialization failed, falling back to non-Sealos mode:', error ); + if (disposed) return; + setState((prev) => ({ ...prev, isInitialized: true, @@ -132,7 +105,9 @@ export function SealosProvider({ children }: { children: React.ReactNode }) { }); return () => { + disposed = true; cleanupRef.current?.(); + cleanupRef.current = null; }; }, []); diff --git a/vitest.config.ts b/vitest.config.ts index 08300a7..4983721 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,7 +6,7 @@ export default defineConfig({ }, test: { environment: 'node', - include: ['**/*.test.ts'], + include: ['**/*.test.ts', '**/*.test.tsx'], env: { SKIP_ENV_VALIDATION: '1', DATABASE_URL: 'https://example.com/fulling-test', From 3fab44627f7ce95e75ac2f232b8a119bedd07a00 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Mon, 6 Jul 2026 19:29:13 +0800 Subject: [PATCH 6/7] fix: retain PVCs when StatefulSet is deleted --- lib/k8s/sandbox-manager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/k8s/sandbox-manager.ts b/lib/k8s/sandbox-manager.ts index 00ea7f5..539396d 100644 --- a/lib/k8s/sandbox-manager.ts +++ b/lib/k8s/sandbox-manager.ts @@ -641,10 +641,10 @@ export class SandboxManager { }, }, minReadySeconds: 10, - // Automatically delete PVCs when StatefulSet is deleted - // This prevents orphaned PVCs and storage costs + // Keep PVCs when StatefulSet is deleted to protect user data if external + // controllers recreate the StatefulSet. deleteSandbox explicitly deletes PVCs. persistentVolumeClaimRetentionPolicy: { - whenDeleted: 'Delete', // Delete PVCs when StatefulSet is deleted + whenDeleted: 'Retain', // Keep PVCs when StatefulSet is deleted whenScaled: 'Retain', // Keep PVCs when scaling down (for potential scale-up) }, template: { From 3de4368603b7758b91af903e16438ea6b0a6c317 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Tue, 7 Jul 2026 11:43:35 +0800 Subject: [PATCH 7/7] fix: remove invalid turbopack config --- next.config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/next.config.ts b/next.config.ts index d390cf1..46354ed 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,6 @@ import type { NextConfig } from 'next' const nextConfig: NextConfig = { - turbopack: false as any, reactStrictMode: true, output: 'standalone', compress: true,