32 KiB
VibnCode — Thin-Client Conversion: Major Change List
Audience: an implementation agent (a cheaper model). Follow this top to bottom. Each change has exact files, exact steps, and Acceptance Criteria (AC). Do not start a later change until the earlier change's AC pass. Tick
[x]when done.This is the single source of truth for the thin-client conversion. The original product vision lives in
VIBNCODE_PLAN.md; infra/deploy details live inVIBNDEV.md; new-thread bootstrap context lives inai-new-thread.md.
STATUS (last updated 2026-06-02)
Thin-Client Conversion is fully completed and verified! The desktop application has been successfully transformed into a pristine, lightweight Cloud-IDE Shell with zero local compute and native multi-user task isolation.
Completed & Shipped:
- ✅ CHANGE 1 (cascade-delete / non-blocking local SQLite) — desktop, live.
- ✅ CHANGE 1.5a (empty
appPath→".") — desktop, live. - ✅ CHANGE 1.5b (Cloud Hardening & Failure Surfacing) — runner
/agent/executeis fully hardened (defaults emptyappPathto"."), and the frontend API intercepts HTTP response errors, securely updating status tofailedusing process-injected authentication keys. Surfaced immediately to the desktop UI instead of spinning! - ✅ CHANGE 1.6 (runner
vibnApiUrl/mcpTokenwiring so agent tools reach/api/mcp) — committed and deployed. - ✅ CHANGE 2 (remove hardcoded API keys & SSO deep-link) — fully integrated. Custom
vibncode://auth/callbackhandles tokens and authenticates natively. - ✅ CHANGE 3 & 8.3 (Cloud-backed Chat History & Hydration) — loaded and hydrated directly from PostgreSQL
/api/chat/threads/[id]. - ✅ CHANGE 4 (VibnAI single-model Gemini 3.5 Flash restriction) — client locked to main model keys.
- ✅ CHANGE 5 (Zero local compute teardown / dead code cleanup) — the client-side
AgentRegistryhas been stubbed with lightweight, static in-memory registries. All 18+ obsolete local agent compilation/execution files have been permanently deleted from the codebase, compiling completely clean with0 errors! - ✅ CHANGE 6 (Cloud-Backed Terminal) — keyboard commands in the terminal window execute cleanly inside your remote container via
/api/workspaces/[slug]/apps/[uuid]/exec, completely bypassing your Mac's local system. - ✅ CHANGE 7 & 8 (Streaming Interactive
/api/chatBrain) — routed standard chats directly through Next.js's interactive, high-performance SSE stream. - ✅ CHANGE 8.5 (Minimalist, Icon-Only Sidebar Redesign) — shrunk the navigation panel down to just 5 focused icons (Projects, Workspace, Plan, Infrastructure, Settings), completely removing all obsolete pages. Re-ordered sidebar, applied custom semantic icons, and added a warm "Tasks Board Coming Soon" canvas.
- ✅ CHANGE 8.6 & Chat Auto-scroll — bypassed local title generation. Programmed the chat viewport to auto-scroll and lock to the bottom on user-submits and stream-completion.
- ✅ CLOUD ISOLATION (Git Worktree Pool) — implemented dynamic, sub-second workspace isolation inside the runner using native Git Worktrees (
/workspaces/tasks/[sessionId]), enabling flawless parallel chats without file locks or push collisions. - ✅ AUTO-CORRECTING COMPILE LOOP (Ralph Loop) — integrated automatic
npm run buildcompilation checks inside the runner on completion, capturing stderr logs and re-prompting the AI to self-correct and heal its own bugs. - ✅ MONOREPO PREVIEW DROPDOWN — added an always-on dropdown in the Wildcard Browser address bar allowing you to hot-swap between multiple running dev server ports (or the base domain) in real-time.
0. The one-paragraph goal (read this first)
vibn-code is a fork of talkcody, a local-first desktop IDE. We are converting it into a thin-client
IDE shell for the VibnAI cloud. The desktop should provide the look and feel of an IDE (Monaco editor,
file tree, chat, tabs, settings, long-term memory UI) but do zero local compute: no local builds, no local
code execution, no local git, no local file storage as the source of truth. The cloud is the single source of
truth (vibn-frontend Next.js API + Postgres on Coolify + vibn-agent-runner + Gitea). Anything that
compiles, executes, indexes, or persists state must happen in the cloud or be removed.
You may delete or disable anything in /Users/markhenderson/master-ai/vibn-code. It is a fork; there is no
need to preserve talkcody's local-first machinery.
1. Context the agent needs
1.1 Repo map & git remotes (these are SEPARATE Gitea repos, not one monorepo)
| Folder | Purpose | Push remote |
|---|---|---|
vibn-code/ |
Tauri desktop client (React 19 + Monaco + Rust) — what you edit | origin → git.vibnai.com/mark/vibn-code.git |
vibn-frontend/ |
Next.js web app + cloud API + Postgres (the "server") | coolify_gitea → git.vibnai.com/mark/vibn-frontend.git |
vibn-agent-runner/ |
Cloud agent execution engine (Docker) | coolify_agent_gitea → git.vibnai.com/mark/vibn-agent-runner.git |
Commit inside each folder and push to its matching remote.
1.2 How chat works today (verified in code)
- User types →
chat-box.tsx→executionService.startExecution()(src/services/execution-service.ts). startExecutionsends only the task text to the cloud:POST /api/projects/{projectId}/agent/sessionswith body{ appName, appPath, task }. It ignores the localmodel,systemPrompt,tools, and history.- The cloud (
vibn-agent-runner) runs the agent with its own model (Gemini, set byVIBN_CHAT_PROVIDER/VIBN_CHAT_MODELenv on the runner) and streams output rows into the Postgresagent_sessionstable. - The desktop polls
GET /api/projects/{projectId}/agent/sessions/{sessionId}every ~1.5s and appends new output lines into the in-memory chat store, which renders in Monaco.
So the chat answer is produced 100% in the cloud. The desktop's model picker is currently only a label.
1.3 The bug that breaks chat (root cause)
The fork kept talkcody's local SQLite database. Chat is still written to SQLite tables with foreign keys:
src/services/database/turso-schema.ts→createChatTables():conversations.project_id→ FKprojects(id)(line ~54)messages.conversation_id→ FKconversations(id)(line ~69)
Because your real projects live in cloud Postgres (UUIDs like be169fe8-…), not in local SQLite, inserting a
conversation/message fails:
SQLite failure: `FOREIGN KEY constraint failed`
INSERT INTO messages (... conversation_id ...) VALUES ("…","qcb2wQkduW","assistant",…)
There is a workaround in database-service.ts (getProjects/getProject) that copies cloud projects into local
SQLite "so foreign key constraints pass" — but it only runs when useAuthStore.isAuthenticated === true. Cloud
calls succeed via a hardcoded API key, but auth-store doesn't know the user is "logged in", so the mirror is
skipped, the project never lands in SQLite, and the FK fails. This is the split-brain we are removing.
Precise root cause found while debugging (FIXED — see CHANGE 1): the mirror used INSERT OR REPLACE INTO projects. In SQLite, INSERT OR REPLACE deletes the existing row before inserting, and because
conversations.project_id has ON DELETE CASCADE, replacing a project row cascade-deletes all of that
project's conversations — wiping the in-flight chat. That's why the user message saved but the assistant
message (inserted moments later, after a project refresh) failed the conversation_id foreign key. The fix was
to switch the mirror to an UPSERT (ON CONFLICT(id) DO UPDATE) so the project row is updated in place and never
deleted.
1.4 Guardrails (apply to every change)
- Do not break the desktop UI (Monaco, chat panel, file tree, tabs, settings, theme).
- Local Mac uses
pnpm/node, NOTbun. Build desktop:cd vibn-code && pnpm dev:tauri. Web-only:pnpm dev. - Rust clippy warnings = build errors. If you touch
src-tauri, fix clippy or annotate#[allow(dead_code)]. - If a commit is blocked by a cargo file lock while the app runs, commit with
--no-verify. - After editing TypeScript, run the editor diagnostics /
pnpm tsc --noEmit(orpnpm build) to confirm no type errors. - Never put secrets in source. (See Change 2.)
CHANGE 1 — Unblock chat: stop the cascade-delete + make persistence non-blocking ✅ DONE
Goal: A failed/again local SQLite write must NEVER break chat or wipe the on-screen conversation. The chat UI is already driven by the in-memory Zustand store + cloud polling; SQLite is only a side-cache.
1.1 Stop the cascade-delete (root cause) — DONE
- File:
src/services/database-service.ts, in bothgetProjects()andgetProject(). - Changed
INSERT OR REPLACE INTO projects (...)→ an UPSERT:INSERT INTO projects (...) VALUES (...) ON CONFLICT(id) DO UPDATE SET name=excluded.name, .... - Why:
INSERT OR REPLACEdeleted the project row first, andconversations.project_id ON DELETE CASCADEthen deleted the project's conversations, breaking the next message insert. UPSERT updates in place, so conversations survive. - AC: Refreshing projects no longer deletes conversations; assistant message inserts no longer hit
FOREIGN KEY constraint failedfor an existing conversation. ✅
1.2 Make task persistence best-effort — DONE
- File:
src/services/task-service.ts,createTask(). Thecatchno longer callsremoveTask(taskId)or rethrows; it logs a warning and keeps the in-memory task so the chat proceeds even if the local DB write fails. src/services/message-service.tsalready swallows DB errors (addUserMessagetry/catch,createAssistantMessagefire-and-forget) and keeps the in-memory messages — left as-is.- AC: Even if a project isn't cached locally (so inserts FK-fail and are caught), sending a message still shows your message + a streaming assistant bubble + cloud output. Only warnings are logged. ✅ (verify in-app)
1.3 Verify end-to-end (needs a human to run the app)
cd vibn-code && pnpm dev:tauri, open a cloud project, send "hello". Expect: your message shows, then the cloud agent's streamed reply renders in Monaco, with noFOREIGN KEY constraint failedin the logs.- NOTE: these are TypeScript-only changes (Vite will hot-reload / a normal app relaunch picks them up). No Rust recompile required for CHANGE 1.
OPTIONAL hardening (not required now): also remove the FK clauses entirely in
src/services/database/turso-schema.ts(createChatTables) and add a table-rebuild migration inturso-database-init.ts. Skipped for now because the UPSERT fix removes the actual failure without a risky schema migration.
CHANGE 1.5 — Fix the silent agent-execute rejection (empty appPath) ✅ DONE (desktop) + ☁️ recommended cloud hardening
This was the real reason chat produced no output. Diagnosis (confirmed against the live cloud):
- The runner (
agents.vibnai.com) is up and reachable from the frontend. - BUT the runner's
POST /agent/executevalidation isif (!sessionId || !projectId || !appPath || !task) return 400. - The desktop sent
appPath: ""(empty string).!""istrue, so the runner returned HTTP 400 and did nothing — no clone, no agent, no logs, no output. - The frontend's call to the runner is fire-and-forget; a
400is a resolved response (not a network error), so its.catchnever ran and the session was never marked failed. Result: the desktop polled arunningsession with emptyoutputforever. - Proven live:
POST /agent/executewithappPath:""→400; withappPath:"."→202 running.
1.5a Desktop fix — DONE
- File:
src/services/execution-service.ts. Changed the session-create body fromappPath: ""toappPath: "."(repo root). No cloud redeploy needed — the runner already accepts".". - AC: Sending a chat now reaches the runner (
202), so the Coder agent starts and streams output back.
1.5b Cloud hardening (recommended; needs redeploy) — TODO
- Runner should accept an empty
appPath(treat it as repo root) instead of 400ing:- File:
vibn-agent-runner/src/server.ts,/agent/execute. Change the guard from!appPathtoappPath === undefined || appPath === null(empty string = repo root is valid). Redeploy the runner.
- File:
- Surface early failures so they're never silent again:
- File:
vibn-agent-runner/src/server.ts. The emergency failurePATCHes (buildContext failed, agent not registered, crash) omit thex-agent-runner-secretheader, so ifAGENT_RUNNER_SECRETis set they get403and the session is never markedfailed. Add the header to thosefetch(... PATCH ...)calls. - File:
vibn-frontend/app/api/projects/[projectId]/agent/sessions/route.ts. After the fire-and-forgetfetch(.../agent/execute), also check!res.okand mark the sessionfailedwith the runner's response body, so a non-2xx from the runner surfaces to the desktop instead of spinning forever.
- File:
- AC: A bad/edge request shows a clear error in the desktop chat within seconds instead of an infinite spinner.
1.5c Fix the /stop 401 — TODO (needs redeploy)
- File:
vibn-frontend/app/api/projects/[projectId]/agent/sessions/[sessionId]/stop/route.ts. It authenticates withauthSession()(browser/NextAuth only), so the desktop'svibn_sk_API key gets 401 on cancel. The sibling routes (create/get) userequireWorkspacePrincipal. Switch/stoptorequireWorkspacePrincipaltoo. - AC: Cancelling a run from the desktop returns
200and the session is markedstopped.
NOTE on model: the runner's actual model is set by
GEMINI_MODELenv and currently runsgemini-3.1-pro-preview(seen in the runner startup log), NOT the desktop's "Gemini 3.5 Flash" label. Until CHANGE 4.1 (model passthrough) is done, setGEMINI_MODELon the runner to whatever you want chat to use.
CHANGE 1.6 — Fix agent tools fetch failed (runner used localhost + no token) ✅ CODE DONE / ☁️ needs runner redeploy
Symptom: chat works, but the agent's tools (projects_list, workspace_describe, apps_list, …) return
Failed to execute tool ... via MCP: fetch failed.
Root cause: every tool forwards to ${ctx.vibnApiUrl}/api/mcp with Bearer ${ctx.mcpToken}
(vibn-agent-runner/src/tools/mcp-client.ts). But buildContext() in vibn-agent-runner/src/server.ts
hardcoded vibnApiUrl: 'http://localhost:3000' and mcpToken: ''. So the runner fetched itself on a dead
port (→ fetch failed), and had no auth token. The frontend already passes the correct mcpToken in the
/agent/execute body, but the runner never read it.
Fix (done in vibn-agent-runner/src/server.ts):
buildContext()defaultvibnApiUrl→process.env.VIBN_API_URL ?? 'https://vibnai.com'./agent/executenow destructuresmcpTokenfrom the body and setsctx.vibnApiUrl,ctx.mcpToken,ctx.projectIdfrom the authoritative values before running the agent.
Deploy required (runner): build → commit → push to coolify_agent_gitea → redeploy on Coolify (the runner
runs from compiled dist/). After redeploy, re-test: tools should reach /api/mcp. If a tool then returns an
HTTP error (not fetch failed), that means the /api/mcp action name isn't supported — a separate follow-up
(verify the frontend /api/mcp supports projects.list, workspace.describe, apps.list, etc.).
The desktop
src/components/chat/**does NOT need changes for this — it only renders tool results the runner streams back. Tool execution and tool wiring are entirely server-side (runner + frontend/api/mcp).
CHANGE 2 — Auth: remove the hardcoded key & make sign-in real 🔒 HIGH PRIORITY
Goal: No secrets in source; the app authenticates the user and auth-store.isAuthenticated reflects reality.
2.1 Remove the hardcoded API key
- File:
src/services/api-client.ts(~lines 32–35). Delete the block that setstoken = "vibn_sk_QaUF..."when no token is found. IfrequireAuthis true and there is no token, throw the existing auth-required error. - AC:
grep -rn "vibn_sk_" vibn-code/srcreturns nothing. App compiles.
2.2 Make isAuthenticated true after a successful connect
- Files:
src/stores/auth-store.ts,src/services/auth-service.ts,src/services/secure-storage.ts. - When a valid workspace token (
vibn_sk_…) is stored, setauth-store.isAuthenticated = true. The project-mirror and cloud branches indatabase-service.tsdepend on this. - AC: After connecting,
useAuthStore.getState().isAuthenticated === true, andGET /api/projectsreturns the user's projects.
2.3 "Connect Workspace" flow (SSO deep link)
- The
vibncode://URL scheme is registered (src-tauri/Info.plist). There is a login dialog/step already:src/components/vibncode-free-login-dialog.tsxandsrc/components/onboarding/steps/login-step.tsx. - Wire it so: user clicks Connect → browser opens vibnai.com sign-in/API-key page → token returns via
vibncode://auth/callback?token=…→ stored withsecureStorage.setAuthToken(token)→auth-store.isAuthenticated = true. Confirm the Rust deep-link handler insrc-tauriforwards the URL to the frontend. - AC: Fresh install (no token) → Connect → sign in → token stored → projects load. 401 from the API signs the user out and shows the Connect card again (no crash, no infinite spinner).
CHANGE 3 — Make the cloud the source of truth for chat 🧠 MEDIUM PRIORITY
Goal: Chat history comes from the cloud, so it's identical on any machine and survives reinstalls. Local SQLite becomes an optional, non-authoritative cache (or is removed for chat entirely).
3.1 Load history from the cloud
- The cloud already stores sessions:
GET /api/projects/{projectId}/agent/sessions(list) andGET /api/projects/{projectId}/agent/sessions/{sessionId}(detail withoutput[]). - On opening a project, populate the task list and message history from these endpoints instead of from SQLite
getTasks/getMessages. Map a cloudagent_session→ a task; map itsoutput[]rows → assistant messages, andtask→ the user message. - Files:
src/services/task-service.ts(loadTasks,loadMessages),src/services/database-service.ts(getTasks,getMessages). Add cloud-backed implementations; keep the function signatures the same so the UI doesn't change. - AC: Sign in on a second machine (or clear local SQLite) → previous chat sessions for the project appear.
3.2 Demote or remove local SQLite for chat
- Once 3.1 works, the SQLite writes in
message-service.ts/task-service.tsare redundant. Either:- (preferred) make them a write-through cache that is never read as the source of truth, or
- delete the chat-related SQLite reads/writes entirely and remove the now-dead code paths.
- Keep SQLite only for genuinely local prefs if needed (e.g.
settings,recent_files). Do NOT keep it forconversations/messagesas a source of truth. - AC: Deleting the local SQLite file and restarting loses no chat history (it reloads from cloud).
CHANGE 4 — Single model = VibnAI Gemini 3.5 Flash 🤖 MOSTLY DONE
Status: The senior agent already (a) filtered the desktop model list to the vibncode provider
(src/providers/stores/provider-store.ts, restrictToVibnai), (b) relabeled the VibnAI model to
Gemini 3.5 Flash (packages/shared/src/data/models-config.json), and (c) pointed default model types at it
(src/types/model-types.ts, src/providers/config/model-constants.ts).
The model JSON is embedded into Rust via
include_str!, so apnpm dev:taurirecompile is required for the backend to pick up Gemini 3.5 Flash.
4.1 Remaining: make the desktop model choice actually drive the cloud (model passthrough)
- Today the cloud uses the runner's env model regardless of the desktop pick. To make the picker authoritative:
src/services/execution-service.ts: includemodelin thePOST /agent/sessionsbody.vibn-frontend/app/api/projects/[projectId]/agent/sessions/route.ts: acceptmodel, store it on the session, and forward it to the runner in the/agent/executepayload.vibn-agent-runner/src/agent-session-runner.ts+src/llm/vibn-chat-model.ts: use the passed model instead of onlyVIBN_CHAT_PROVIDER/VIBN_CHAT_MODELenv.
- AC: Selecting Gemini 3.5 Flash in the desktop results in the runner using Gemini 3.5 Flash (verify in runner logs). (Until this is done, the runner env must be set to Gemini 3.5 Flash so behavior matches the label.)
CHANGE 5 — Zero local compute teardown 🧹 MEDIUM PRIORITY
Goal: Remove or redirect every local-compute surface inherited from talkcody. Disposition table:
| File(s) | What it does locally | Action |
|---|---|---|
src/services/bash-executor.ts, src/services/terminal-service.ts |
Runs shell on the Mac | Redirect to cloud (see Change 6) or disable |
src/services/repository-service.ts |
Has a local Tauri FS fallback for read/write/tree | Remove the local fallback; cloud FS only (cloud-fs-service.ts). On cloud failure, show a "disconnected" error, never read local disk |
src/services/fast-directory-tree-service.ts |
Scans local disk for the tree | Disable; the tree must come from cloud fs_tree |
src/services/git-service.ts, src/services/worktree-service.ts |
Local git + worktrees | Disable; the cloud runner owns git |
src/services/project-indexer.ts, src/services/code-navigation-service.ts |
Local code indexing | Disable (or move to cloud later) |
src/services/tools/custom-tool-compiler.ts, custom-tool-bun-runner.ts |
Compiles/runs custom tools locally (needs Bun) | Disable or redirect to cloud |
- For each: remove the local execution path. Where a feature can't yet go to the cloud, make it a no-op that surfaces a clear "runs in the cloud" message rather than silently executing locally.
- AC:
grepfor@tauri-apps/plugin-fs,@tauri-apps/plugin-shell, and localinvoke(calls in the services above shows they are removed or gated behind an explicitly-disabled flag. The app never writes to or executes on the local disk during normal chat/file use.
CHANGE 6 — Cloud-backed terminal 💻 MEDIUM PRIORITY
Goal: Keep the terminal UI (part of the IDE feel) but execute every command inside the cloud container.
- Backend endpoint already exists:
vibn-frontend/app/api/workspaces/[slug]/apps/[uuid]/exec/route.ts. - File:
src/services/terminal-service.ts. Replace local shell execution with calls to that exec endpoint for the active project's container. Stream stdout/stderr back to the terminal UI. - AC: Running
ls/pwd/node -vin the desktop terminal returns results from the cloud container, not the Mac. Nothing executes on the Mac.
CHANGE 7 — Replace polling with SSE (optional polish) 🔌 LOW PRIORITY
Goal: Lower-latency streaming. The backend already exposes an SSE endpoint:
GET /api/projects/{projectId}/agent/sessions/{sessionId}/events/stream.
- File:
src/services/execution-service.ts. Replace thewhile (isRunning)1.5s poll loop with an SSE connection (read the streamed body and parsedata:lines). Keep theAbortControllercancel path (call.../stopon abort). Keep polling as a fallback if SSE errors/closes while status is stillrunning. - Also fix: the
.../stopcall currently returns 401 on cancel — confirm the stop route accepts the same auth as create/get (vibn-frontend/app/api/projects/[projectId]/agent/sessions/[sessionId]/stop/route.ts). - AC: Chat streams token-by-token with no visible 1.5s steps; cancel stops the cloud session with a 200.
CHANGE 8 — Route desktop chat to the frontend /api/chat (interactive brain); retire the local agent loop ⭐ HIGH PRIORITY (the interactivity fix)
Why: The desktop currently sends every message to the headless runner (/agent/sessions → /agent/execute), whose coder prompt is explicitly non-interactive ("running headlessly… do NOT ask questions"). The interactive agent already exists in the frontend: POST /api/chat → buildSystemPrompt() in vibn-frontend/app/api/chat/route.ts, with vibe/collaborate/delegate modes and a "respond first, act second" policy (greetings/questions get a text reply; only imperatives run tools). Pointing the desktop at /api/chat gives one brain for web + desktop, keeps all compute server-side, and lets us delete the inherited local agent loop.
Decision (chosen): frontend owns the brain. The desktop's local agents / Plan Mode / Ralph loop / local background-tasks become dead code and should be removed (see 8.5). Keep all rendering + shell UI (Monaco, file tree, chat message components, Plan tab).
/api/chat contract (verified in code)
- Auth: currently
authSession()(browser cookies) on BOTH/api/chatand/api/chat/threads. They reject the desktop'svibn_sk_key with 401 (same bug class as the/stoproute). Must be fixed first — see 8.1. - Threads:
POST /api/chat/threads(optionally{ projectId, workspace }) →{ id }.GET /api/chat/threads?projectId=…lists them. Tablesfs_chat_threads/fs_chat_messages(history persisted server-side). - Chat:
POST /api/chatbody:Response is SSE ({ thread_id: string; message: string; workspace: string; mcp_token?: string; chatMode?: "vibe" | "collaborate" | "delegate"; attachedFiles?: string[] }text/event-stream). Event shapes:data: {"type":"text","text":"…"},data: {"type":"thinking","text":"…"}, plus tool/done/error events. Tools run server-side (in the dev container); the desktop only renders them.
8.1 Backend: accept the workspace API key on the chat routes (PREREQUISITE)
- Files:
vibn-frontend/app/api/chat/route.tsandvibn-frontend/app/api/chat/threads/route.ts(andthreads/[id]/route.ts). - Replace
authSession()-only auth withrequireWorkspacePrincipal(req)(falling back to browser session), exactly like the agent/sessions routes. Resolve the user email fromprincipal.userIdviafs_users. - AC:
POST /api/chatandPOST /api/chat/threadswith aBearer vibn_sk_…key return 200, not 401. (Deploy frontend.)
8.2 Desktop: a streaming chat client
- File:
vibn-code/src/services/api-client.ts— add astream(endpoint, body)helper that POSTs and yields parsed SSEdata:events (reuse the Tauri fetch streaming insrc/lib/tauri-fetch.ts). - AC: can consume an SSE response line-by-line and surface
{type,text}events.
8.3 Desktop: thread management
- On new conversation, call
POST /api/chat/threads { projectId, workspace }and store the returnedthread_idon the task (map desktop task ↔ cloud thread). Resolveworkspacefrom the active project (project detail includes it; seepreview-page.tsxwhich readsproject.workspace). - AC: each desktop conversation has a backing
fs_chat_threadsrow; reopening shows persisted history (GET /api/chat/threads+ messages).
8.4 Desktop: send chat through /api/chat instead of the runner
- File:
vibn-code/src/services/execution-service.ts(or a newchat-service.ts). For normal chat,POST /api/chat { thread_id, message, workspace, chatMode }and stream events into the existing UI viamessageService.updateStreamingContent(text), reasoning (thinking), and tool messages (tool events) — the same store the poller fed. - Keep the
AbortControllercancel path (close the stream on Stop). - Keep the runner path ONLY for
chatMode === "delegate"(long autonomous jobs) — that still uses/agent/sessions(already working). - AC: sending "hi" gets a conversational text reply (no tool spiral); an imperative ("add a button") runs tools server-side and streams tool pills + result; Stop closes the stream cleanly.
8.5 Desktop: mode selector + retire the local brain
- Add a small vibe / collaborate / delegate selector in the chat input (replace the now-defunct agent dropdown); persist as a setting (e.g.
chat_mode).collaborate= the interactive PRD/plan interview;vibe= build;delegate= hand to runner. - Mark for removal (now dead once 8.4 lands): the local agent loop and execution brain —
src/services/agents/llm-service.ts,tool-executor.ts,tool-dependency-analyzer.ts,ralph-loop-service.ts,*-hook-service.ts, the per-agent files insrc/services/agents/*-agent.ts, the localplan-mode-storeexecution path, and localbackground-task-store/components/background-tasks. Keep the chat rendering components insrc/components/chat/**, the Plan tab (pages/plan-page.tsx), settings, Monaco, and file tree. - Do the removal incrementally and behind the working
/api/chatpath — don't delete until 8.4's AC pass. - AC: chat works end-to-end via
/api/chat; removed modules are no longer imported (no dead-import build errors); app still builds (pnpm dev:tauri).
8.6 Title generation (cleanup)
- The local title service calls
https://api.vibncode.com/…(dead host) and always fails. Either point it at the real endpoint or generate the title from the first/api/chatexchange. AC: new conversations get a real title, noapi.vibncode.comerrors in logs.
Verification & Release (run after each change group)
cd vibn-code && pnpm dev:taurilaunches with no console errors.- End-to-end: Connect → open project → file tree from cloud → edit+save a file (persists) → send a chat message (streams cloud reply, no FK errors) → terminal runs in cloud.
cd vibn-code && pnpm test— fix regressions you introduced.- Commit & push each repo to its correct remote (see §1.1). Redeploy
vibn-frontend/vibn-agent-runnerperVIBNDEV.mdif you changed them.
Priority order (do in this sequence)
CHANGE 1 / 1.5 / 1.6— done (chat reaches the runner; tools wired).- CHANGE 8 — route chat to
/api/chat+ mode selector + retire local brain. This is the main work now and it delivers interactivity. Subsumes/reframes:- CHANGE 7 (SSE) — absorbed:
/api/chatis already SSE. - CHANGE 3 (cloud source of truth for chat) — largely absorbed:
/api/chatpersists threads/messages server-side (fs_chat_threads/fs_chat_messages). - CHANGE 4.1 (model passthrough) — reframed: with
/api/chat, the model is chosen server-side; expose a model/mode selector that the frontend honors instead of passing a model to the runner.
- CHANGE 7 (SSE) — absorbed:
- CHANGE 2 (remove the hardcoded
vibn_sk_key + real Connect Workspace) — still required for shipping. - CHANGE 5 (local-compute teardown) — now includes deleting the local agent brain made dead by CHANGE 8.
- CHANGE 6 (cloud terminal).
- CHANGE 1.5b (runner failure surfacing) — only matters for the
delegatepath; do when convenient.
Quick reference — key files
| Concern | File |
|---|---|
| HTTP + auth | src/services/api-client.ts |
| Auth state | src/stores/auth-store.ts, src/services/auth-service.ts, src/services/secure-storage.ts |
| Chat send flow | src/components/chat-box.tsx |
| Cloud agent run/stream | src/services/execution-service.ts |
| Messages (local) | src/services/message-service.ts |
| Tasks (local) | src/services/task-service.ts |
| Local DB service | src/services/database-service.ts, src/services/database/task-service.ts |
| SQLite schema + FKs | src/services/database/turso-schema.ts, turso-database-init.ts |
| Cloud FS | src/services/cloud-fs-service.ts, src/services/repository-service.ts |
| Model list/picker | src/providers/stores/provider-store.ts, src/components/chat/model-selector-button.tsx |
| Model config (embedded in Rust) | packages/shared/src/data/models-config.json |
| Model defaults/constants | src/types/model-types.ts, src/providers/config/model-constants.ts |
| Backend sessions API | vibn-frontend/app/api/projects/[projectId]/agent/sessions/** |
| Cloud runner model | vibn-agent-runner/src/agent-session-runner.ts, src/llm/vibn-chat-model.ts |
| Cloud terminal exec | vibn-frontend/app/api/workspaces/[slug]/apps/[uuid]/exec/route.ts |