-
Notifications
You must be signed in to change notification settings - Fork 4
Update project files #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
khulnasoft-bot
wants to merge
4
commits into
main
Choose a base branch
from
v0/khulnasoft-1c9603f3-2
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export const config = { | ||
| maxDuration: 60, | ||
| }; | ||
|
|
||
| import taskcoreVercelHandler from "../server/src/vercel.ts"; | ||
|
|
||
| export default taskcoreVercelHandler; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| # Deploying Taskcore to Vercel | ||
|
|
||
| Status: Supported deployment target | ||
| Date: 2026-07-31 | ||
|
|
||
| ## 1. Overview | ||
|
|
||
| Taskcore can run as a Vercel project with three deployable parts: | ||
|
|
||
| | Part | What deploys | Where it comes from | | ||
| |---|---|---| | ||
| | **API** | A single Node serverless function at `api/index.js` | `server/src/vercel.ts` bundled by `scripts/build-vercel-function.mjs` (esbuild) | | ||
| | **UI** | Static SPA build in `ui/dist` | `pnpm --filter @taskcore/ui build` | | ||
| | **Database** | External PostgreSQL | Vercel Postgres, Neon, Supabase, or any reachable Postgres via `DATABASE_URL` | | ||
|
|
||
| `vercel.json` wires the three together: | ||
|
|
||
| - `buildCommand`: `pnpm vercel:build` — builds workspace packages, the UI, then the serverless bundle | ||
| - `outputDirectory`: `ui/dist` — static UI served from the build output | ||
| - `rewrites`: `/api/*` goes to the function; everything else falls back to `index.html` (SPA routing) | ||
| - `functions.api/index.js.maxDuration`: 60s so a cold boot (Express + better-auth + DB pool) completes before the first response | ||
|
|
||
| The Express app serves the API only on Vercel (`SERVE_UI=false`, `uiMode: "none"`). All UI paths are served statically by the platform. | ||
|
|
||
| ## 2. What Works / What Does Not | ||
|
|
||
| Works on Vercel: | ||
|
|
||
| - Full `/api/*` surface (board routes, agent routes, better-auth `/api/auth/*`) | ||
| - Static board UI at the deployment root | ||
| - External PostgreSQL (`DATABASE_URL`, `POSTGRES_URL`/`POSTGRES_URL_NON_POOLING`, or `PGHOST`/`PGDATABASE`/`PGUSER`/`PGPASSWORD`) | ||
| - S3 storage via `TASKCORE_STORAGE_PROVIDER=s3` (see `doc/DATABASE.md`-adjacent storage docs) | ||
|
|
||
| Not available in the serverless runtime (the Vercel handler disables these): | ||
|
|
||
| - Embedded PostgreSQL / PGlite — `DATABASE_URL` is **required** (`server/src/vercel.ts` fails boot without it) | ||
| - Long-lived background work: heartbeat scheduler, routine scheduler, database backups, plugin workers | ||
| - Live events WebSocket channel (polling endpoints still work) | ||
| - Local-disk storage (`/tmp` is ephemeral — uploads/assets are lost between cold starts) | ||
| - Local/embedded agent adapters (Claude, Codex, etc. run as processes — they cannot run in a serverless function) | ||
|
|
||
| ## 3. Prerequisites | ||
|
|
||
| - Repo pushed to GitHub, imported into a Vercel project | ||
| - An external PostgreSQL database (Vercel Postgres storage is the simplest path — it sets `POSTGRES_URL` automatically) | ||
| - Node.js 20+ and pnpm 9+ for local builds | ||
|
|
||
| ## 4. Required Environment Variables | ||
|
|
||
| | Variable | Required | Purpose | | ||
| |---|---|---| | ||
| | `DATABASE_URL` (or `POSTGRES_URL` / `POSTGRES_URL_NON_POOLING` / `PGHOST`+`PGDATABASE`+`PGUSER`+`PGPASSWORD`) | Yes | External Postgres connection | | ||
| | `BETTER_AUTH_SECRET` (or `TASKCORE_AGENT_JWT_SECRET`) | Yes | Auth cookie/JWT signing secret | | ||
| | `TASKCORE_AUTH_PUBLIC_BASE_URL` (or `BETTER_AUTH_URL`) | Yes | Public deployment URL, e.g. `https://taskcore-<project>.vercel.app` | | ||
|
|
||
| Defaults applied automatically when `VERCEL=1` (see `applyVercelDefaults` in `server/src/vercel.ts`): | ||
|
|
||
| | Variable | Default | Notes | | ||
| |---|---|---| | ||
| | `TASKCORE_DEPLOYMENT_MODE` | `authenticated` | Public unauthenticated boards are rejected | | ||
| | `TASKCORE_DEPLOYMENT_EXPOSURE` | `public` | | | ||
| | `SERVE_UI` | `false` | UI is served statically by Vercel | | ||
| | `TASKCORE_PLUGINS_ENABLED` | `false` | Plugin workers cannot run serverless | | ||
| | `TASKCORE_DB_BACKUP_ENABLED` | `false` | | | ||
| | `HEARTBEAT_SCHEDULER_ENABLED` | `false` | | | ||
| | `TASKCORE_STORAGE_LOCAL_DIR` | `/tmp/taskcore-storage` | Ephemeral — set S3 storage for durable uploads | | ||
| | `TASKCORE_PG_MAX_CONNECTIONS` | `5` | Keep bounded for serverless concurrency | | ||
|
|
||
| Recommended: `TASKCORE_STORAGE_PROVIDER=s3` with `TASKCORE_STORAGE_S3_BUCKET`, `TASKCORE_STORAGE_S3_REGION`, and `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` so attachments and assets survive cold starts. | ||
|
|
||
| ## 5. Deploy Steps | ||
|
|
||
| 1. Import the repo into Vercel (framework preset: **Other**; the repo's `vercel.json` overrides settings). | ||
| 2. Create an external Postgres (or attach Vercel Postgres) and set the env vars above, including the deployment URL in `TASKCORE_AUTH_PUBLIC_BASE_URL`. | ||
| 3. Deploy. `pnpm vercel:build` runs on Vercel: workspace packages → UI (`ui/dist`) → serverless bundle (`api/index.js`). | ||
| 4. Migrations are **not** applied by the serverless runtime. Before first use, apply the schema once: | ||
| ```sh | ||
| DATABASE_URL=... pnpm db:migrate | ||
| ``` | ||
| 5. Sign in with a real user at `https://<deployment>/api/auth/sign-in/email` (via the UI login page) — the first admin becomes the instance admin (board claim flow in `authenticated` mode). | ||
|
|
||
| ## 6. Local Verification | ||
|
|
||
| ```sh | ||
| pnpm vercel:build # full production build (workspace + UI + function bundle) | ||
| vercel dev # run the deployed layout locally (API function + static UI) | ||
| ``` | ||
|
|
||
| `vercel build` locally validates the exact layout (`ui/dist` static output + `api/index.js` function) that the platform will serve. | ||
|
|
||
| ## 7. Repository Files | ||
|
|
||
| - `vercel.json` — platform config (build, routes, function limits) | ||
| - `server/src/vercel.ts` — serverless Express handler with Vercel runtime defaults and config guards | ||
| - `scripts/build-vercel-function.mjs` — esbuild bundler for `api/index.js` | ||
| - `packages/shared/src/vercel-postgres.ts` — `DATABASE_URL`/`POSTGRES_URL`/`PGHOST` connection resolution | ||
| - `api/index.js` — generated bundle (gitignored; built during `vercel:build`) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| /** | ||
| * Resolve a PostgreSQL connection URL from the environment. | ||
| * | ||
| * Vercel exposes the database connection through several conventions: | ||
| * - `DATABASE_URL` (explicit; always wins) | ||
| * - `POSTGRES_URL` / `POSTGRES_URL_NON_POOLING` (Vercel Postgres) | ||
| * - `PGHOST`/`PGPORT`/`PGUSER`/`PGPASSWORD`/`PGDATABASE`/`PGSSLMODE` (AWS RDS integration) | ||
| * | ||
| * Returns `undefined` when no usable connection is configured. | ||
| */ | ||
| export function resolvePostgresUrlFromEnv(): string | undefined { | ||
| const direct = process.env.DATABASE_URL?.trim(); | ||
| if (direct) return direct; | ||
|
|
||
| const pooled = process.env.POSTGRES_URL?.trim(); | ||
| if (pooled) return pooled; | ||
|
|
||
| const nonPooling = process.env.POSTGRES_URL_NON_POOLING?.trim(); | ||
| if (nonPooling) return nonPooling; | ||
|
|
||
| const host = process.env.PGHOST?.trim(); | ||
| const database = process.env.PGDATABASE?.trim(); | ||
| if (!host || !database) return undefined; | ||
|
|
||
| const user = encodeURIComponent(process.env.PGUSER?.trim() || "postgres"); | ||
| const password = process.env.PGPASSWORD ? encodeURIComponent(process.env.PGPASSWORD) : ""; | ||
| const port = process.env.PGPORT?.trim() || "5432"; | ||
|
|
||
| const auth = `${user}${password ? `:${password}` : ""}`; | ||
| let url = `postgres://${auth}@${host}:${port}/${database}`; | ||
|
|
||
| const sslMode = process.env.PGSSLMODE?.trim(); | ||
| if (sslMode && sslMode !== "disable") { | ||
| url += "?sslmode=require"; | ||
| } | ||
|
|
||
| return url; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| #!/usr/bin/env node | ||
| /** | ||
| * Builds the Vercel serverless function bundle. | ||
| * | ||
| * The Taskcore control plane runs as a long-lived Express server locally. For | ||
| * Vercel we compile the server into a single self-contained ESM module that | ||
| * exports a `(req, res)` handler and place it at `api/index.js`, which Vercel | ||
| * deploys as a Node.js Function. The static UI is deployed separately from | ||
| * `ui/dist` (see `vercel.json`). | ||
| * | ||
| * Workspace packages (`@taskcore/*`) export TypeScript source, so they must be | ||
| * bundled rather than resolved at runtime. npm dependencies stay external so | ||
| * Vercel's file tracer can include them from `node_modules`. | ||
| */ | ||
| import { build } from "esbuild"; | ||
| import { existsSync, readFileSync } from "node:fs"; | ||
| import { mkdir } from "node:fs/promises"; | ||
| import path from "node:path"; | ||
| import { fileURLToPath } from "node:url"; | ||
|
|
||
| const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); | ||
| const entry = path.join(repoRoot, "server/src/vercel.ts"); | ||
| const outfile = path.join(repoRoot, "api/index.js"); | ||
|
|
||
| if (!existsSync(entry)) { | ||
| console.error(`[vercel] entry not found: ${entry}`); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| await mkdir(path.dirname(outfile), { recursive: true }); | ||
|
|
||
| const serverPkg = JSON.parse( | ||
| readFileSync(path.join(repoRoot, "server/package.json"), "utf8"), | ||
| ); | ||
|
|
||
| const nativeExternal = [ | ||
| "sharp", | ||
| "@img/*", | ||
| "embedded-postgres", | ||
| "@vercel/node", | ||
| "pg-native", | ||
| "vite", | ||
| "jsdom", | ||
| ]; | ||
|
|
||
| /** | ||
| * Keep every npm dependency external so the bundle stays ESM-clean (no | ||
| * esbuild CJS `require` shims that break in the Vercel Node runtime) and so | ||
| * Vercel's file tracer can include them from node_modules. Workspace | ||
| * packages (`@taskcore/*`) resolve to TypeScript source outside node_modules | ||
| * and stay bundled, which is the whole point of the single-file function. | ||
| */ | ||
| const externalizeNodeModules = { | ||
| name: "externalize-node-modules", | ||
| setup(build) { | ||
| // One shared verdict per package name so concurrent importers agree on | ||
| // external vs bundled (esbuild processes resolve callbacks in batches). | ||
| const verdicts = new Map(); | ||
| const resolveVerdict = (args) => { | ||
| const existing = verdicts.get(args.path); | ||
| if (existing) return existing; | ||
| const verdict = build | ||
| .resolve(args.path, { | ||
| importer: args.importer, | ||
| resolveDir: args.resolveDir, | ||
| kind: args.kind, | ||
| }) | ||
| .then((result) => { | ||
| if (result.errors.length > 0) { | ||
| return { errors: result.errors }; | ||
| } | ||
| if (result.path.includes("/node_modules/")) { | ||
| return { path: args.path, external: true }; | ||
| } | ||
| return null; | ||
| }) | ||
| .finally(() => verdicts.delete(args.path)); | ||
| verdicts.set(args.path, verdict); | ||
| return verdict; | ||
| }; | ||
| build.onResolve({ filter: /^[^./]/ }, (args) => resolveVerdict(args)); | ||
| }, | ||
| }; | ||
|
|
||
| try { | ||
| await build({ | ||
| entryPoints: [entry], | ||
| outfile, | ||
| bundle: true, | ||
| platform: "node", | ||
| format: "esm", | ||
| target: "node20", | ||
| external: nativeExternal, | ||
| plugins: [externalizeNodeModules], | ||
| logLevel: "info", | ||
| legalComments: "none", | ||
| define: { | ||
| "process.env.NODE_ENV": JSON.stringify("production"), | ||
| "process.env.TASKCORE_SERVER_VERSION": JSON.stringify(serverPkg.version ?? "0.0.0"), | ||
| }, | ||
| banner: { | ||
| js: "/* Taskcore Vercel serverless bundle. Generated by scripts/build-vercel-function.mjs - do not edit. */", | ||
| }, | ||
| }); | ||
| console.log(`[vercel] serverless bundle written to ${outfile}`); | ||
| } catch (err) { | ||
| console.error("[vercel] failed to build serverless bundle:", err); | ||
| process.exit(1); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (bug_risk): Double
unknowncast hides type mismatches between Hermes skill APIs andServerAdapterModule.The
unknown→ServerAdapterModulecasts bypass TypeScript’s structural checks, so any mismatch between the Hermes helpers and the expected signatures will only fail at runtime. Instead, update the Hermes helper types (orServerAdapterModuleif Hermes is canonical) so they are directly compatible without unsafe casts.Suggested implementation:
To fully implement the suggestion (and surface any real type mismatches instead of hiding them), you should also:
ServerAdapterModule, e.g.const hermesAdapter: ServerAdapterModule = { ... }. This will make TypeScript check thathermesListSkillsandhermesSyncSkillsmatch the required signatures.hermesListSkillsandhermesSyncSkillsin their respective modules so that they are structurally compatible withServerAdapterModule["listSkills"]andServerAdapterModule["syncSkills"](parameters, return types, and async/Promise shape).ServerAdapterModuleinterface instead so itslistSkillsandsyncSkillsdefinitions match the Hermes helpers, then let this registry file rely on standard type inference without casts.