Completes the rest of the Path B tool surface:
- dev_server.{start,stop,list,logs}: nohup processes inside the dev
container, track PID/port/preview-url in fs_dev_servers. Each gets
a randomized preview subdomain (preview.vibnai.com base; Traefik
wildcard wiring is staged in /vibn-dev/PREVIEWS.md but the Coolify
compose hot-update step is deferred — see file for the recommended
pre-allocated-port-range approach).
- ship: git init (if needed) -> add/commit/push to the project's
Gitea repo via the workspace bot PAT, then triggers a Coolify
production deploy if the project is linked to one. Returns push
output + deployment_uuid.
- /api/admin/path-b/autosave [POST { projectId | sweep:true }]:
force-pushes /workspace to vibn-autosave/main in Gitea. Throttled
to once per 5 min per project. Records every push in fs_dev_autosaves
for audit. Treat Gitea as canonical, container disk as ephemeral.
- /api/admin/path-b/idle-sweep [POST?minutes=30]: suspends every
running dev container whose last_active_at is older than `minutes`.
Wire to a 5-min cron. Idempotent.
- Compose template hardened: pull_policy: never (use locally-built
image, no registry round-trip) + per-project bridge network
(vibn-dev-net-<slug>) so dev containers can't reach internal Vibn
services.
- vibn-dev/setup-on-coolify.sh: one-shot script to build vibn-dev:latest
on the Coolify host. Run before first chat session uses Path B.
- vibn-tools.ts: dev_server_{start,stop,list,logs} + ship Gemini tool
defs added. Smoke test passes — 68 tool definitions accepted.
- MCP version 2.5.0 -> 2.6.0 so /api/mcp tells us when the new build
is live.
Plan doc updated to reflect what shipped vs what's still manual
(DNS wildcard, Traefik cert, build-on-host script run, gitea_file_*
hard-remove deferred to allow A/B).
Made-with: Cursor
Kicks off Path B (AI_PATH_B_EXECUTION_PLAN.md): each Vibn project gets
its own vibn-dev Coolify service that the AI drives directly via shell
and filesystem tools. Sub-second iteration vs the 5-min Gitea redeploy
loop.
What's in this commit (week 1, slice 1):
- vibn-dev Dockerfile: small Ubuntu base (~500 MB target). git, ripgrep,
python3, mise. Language toolchains lazy-install on first use.
- lib/dev-container.ts: ensureDevContainer / suspend / resume /
execInDevContainer. Backed by a new fs_project_dev_containers table.
- lib/feature-flags.ts + /api/admin/path-b/{disable,enable}: kill switch.
Bearer NEXTAUTH_SECRET flips path_b_disabled, propagates in ~10s.
- New MCP tools wired into /api/mcp: devcontainer.{ensure,status,suspend},
shell.exec, fs.{read,write,edit,list,delete,glob,grep}. All enforce
workspace isolation via fs_projects ownership check.
- vibn-tools.ts: 11 new Gemini tool defs (smoke test passes, 63 total).
- chat system prompt: shell-first guidance; gitea_file_* marked
deprecated for iterative work (still available, removed week 3).
Safety nets baked in:
- pathBGuard() returns 503 from every Path B tool when the kill switch
flips
- fs.* paths locked to /workspace
- ensureResourceInWorkspaceProjects via fs_project_dev_containers PK
- per-project resource limits (1 vCPU, 1 GiB RAM) on the compose spec
Still pending (queued):
- dev_server.* (preview URLs through Traefik)
- ship tool (push to Gitea + trigger prod deploy)
- auto-push autosave to vibn-autosave/main every 5 min
- idle-suspend cron after 30 min inactivity
- HMR-through-Traefik spike
- eval harness
Made-with: Cursor
Closes the AI's self-reported gap: "I cannot directly commit or push code".
New MCP capabilities (8) — all scoped to the workspace's Gitea org via
requireGiteaOrg + ensureRepoOwnerInOrg:
- gitea.repos.list — discover existing repos
- gitea.repo.get — metadata (default branch, clone URL)
- gitea.repo.create — mint a new private repo with auto-init
- gitea.file.read — read a file (or list a directory)
- gitea.file.write — create/update one file in one commit
- gitea.file.delete — delete a file (auto-resolves sha)
- gitea.branches.list — list branches with head sha
- gitea.branch.create — branch off an existing branch
Wired through:
- lib/gitea.ts: giteaReadFile, giteaListContents, giteaListBranches,
giteaCreateBranch, giteaListOrgRepos, giteaDeleteFile.
- lib/ai/vibn-tools.ts: 8 new Gemini tool declarations (53 total).
- app/api/chat/route.ts: system prompt now teaches the end-to-end
scaffold-then-deploy recipe so the AI stops deferring to the user.
MCP capability descriptor bumped to version 2.5.0.
Made-with: Cursor
Each Vibn project now gets its OWN Coolify project named
vibn-{workspace-slug}-{project-slug}. All apps/databases/services
deployed for the project land inside that Coolify project, giving
us clean grouping, cascading delete, and per-project domain
namespaces.
Changes:
- New lib/projects.ts: ensureProjectCoolifyProject (idempotent
create/lookup), getProjectCoolifyUuid, getOwnedCoolifyProjectUuids
- /api/projects/create: pre-insert row, mint per-project Coolify
project, then complete the row with productData (preserves the
coolifyProjectUuid that was just set)
- apps.list (MCP): without projectId, aggregates across ALL
workspace-owned Coolify projects; with projectId, scopes to
that project's Coolify project. Returns coolifyProjectUuid
on each result so the AI knows where things live.
- apps.create (MCP): accepts projectId; auto-mints the Vibn
project's Coolify project on first deploy if missing
- apps_list/apps_create tool defs: projectId param surfaced
- System prompt: Project as first-class — planning + live as
facets of ONE thing, never as separate worlds. AI told to
always pass projectId on apps_create.
Stage 2 (next): set-aware ensureResourceInProject across all
single-resource MCP tools (apps.get/delete/exec/etc.) and
cascading delete via projects.delete.
Made-with: Cursor
Gemini's function_declarations validator requires:
- ARRAY types must declare items schema
- Free-form OBJECT (without properties) is rejected
Renamed free-object params to *Json string fields (envsJson, patchJson,
headersJson) and added server-side JSON.parse before forwarding to MCP.
Any param ending in "Json" is automatically unpacked into its real key
(e.g. envsJson string is parsed into envs object).
Made-with: Cursor
The Gemini REST API returns thoughtSignature as a sibling part field:
{ "functionCall": {...}, "thoughtSignature": "..." }
not inside functionCall. We were reading part.functionCall.thought_signature
(always undefined) and writing fc.thought_signature inside the functionCall
object (also wrong). Now correctly reads part.thoughtSignature and writes
part.thoughtSignature when building history.
Made-with: Cursor
Gemini 3.1 Pro thinking model requires thought_signature to be echoed
in functionResponse. SSE stream doesn't reliably include it in individual
chunks. Switch tool-calling rounds to non-streaming generateContent which
always returns the complete response with thought_signature present.
Made-with: Cursor
Thinking models attach a thought_signature to functionCall parts.
Must be echoed back in functionResponse or API returns 400.
Carry it through ToolCall -> ChatMessage -> toGeminiContents().
Made-with: Cursor
Gemini can now:
- Search GitHub for MIT-licensed OSS repos matching any description
- Read specific files from any public repo (READMEs, design systems,
package.json, docker-compose.yml, component libraries, etc.)
- Fetch any public URL for docs, APIs, or reference material
No hardcoded pipelines — Gemini decides how to use these tools
based on what the user asks for.
Made-with: Cursor
- Right-docked chat panel on all workspace pages ([workspace]/layout.tsx)
- Streaming SSE responses with Gemini 3.1 Pro preview via generativelanguage API
- Full tool-calling loop (up to 6 rounds): deploys apps, lists projects, registers
domains, fetches logs — all via existing MCP dispatcher
- Persistent conversation history: fs_chat_threads + fs_chat_messages tables (Postgres)
- Thread management: create, list, rename (auto-title from first message), delete
- Panel collapses to a tab; open state persisted to localStorage
- Read-only mode hint when no MCP token is present
- Graceful content margin shift when panel is open
Made-with: Cursor
- gemini-client.ts: replaces Vertex AI REST + service account auth with
direct generativelanguage.googleapis.com calls using GOOGLE_API_KEY.
Removes all Firebase credential setup code.
- summarize/route.ts: same migration, simplified to a single fetch call.
- No longer depends on gen-lang-client-0980079410 GCP project for AI calls.
Co-authored-by: Cursor <cursoragent@cursor.com>