diff --git a/app.py b/app.py
index 0af6b18ea..22b63cc82 100644
--- a/app.py
+++ b/app.py
@@ -529,9 +529,6 @@ upload_cleanup_task = None
from routes.emoji_routes import setup_emoji_routes
app.include_router(setup_emoji_routes())
-from routes.workspace_routes import setup_workspace_routes
-app.include_router(setup_workspace_routes())
-
# Sessions
from routes.session_routes import setup_session_routes
session_config = {"REQUEST_TIMEOUT": REQUEST_TIMEOUT, "OPENAI_API_KEY": OPENAI_API_KEY, "SESSIONS_FILE": SESSIONS_FILE}
@@ -650,10 +647,6 @@ app.include_router(calendar_router)
from routes.shell_routes import setup_shell_routes
app.include_router(setup_shell_routes())
-# Terminal agents (tmux-backed Codex/Claude/shell sessions)
-from routes.terminal_agent_routes import setup_terminal_agent_routes
-app.include_router(setup_terminal_agent_routes())
-
# Cookbook (model download/serve/cache, cookbook state sync)
from routes.cookbook_routes import setup_cookbook_routes
app.include_router(setup_cookbook_routes())
diff --git a/routes/chat_routes.py b/routes/chat_routes.py
index 9880522c8..39c17ec6c 100644
--- a/routes/chat_routes.py
+++ b/routes/chat_routes.py
@@ -456,12 +456,7 @@ def setup_chat_routes(
# manual form posts that still send plan_mode=true.
plan_mode = False
chat_mode = str(form_data.get("mode", "")).lower() # 'chat' or 'agent'
- # Workspace: confine the agent's file/shell tools to this folder. Validate
- # it's a real directory; ignore (no confinement) otherwise.
- workspace = (form_data.get("workspace") or "").strip()
- if workspace:
- _ws_real = os.path.realpath(os.path.expanduser(workspace))
- workspace = _ws_real if os.path.isdir(_ws_real) else ""
+ workspace = ""
# Plan mode is a modifier on agent mode — it only makes sense with tools.
if plan_mode:
chat_mode = "agent"
@@ -1140,7 +1135,7 @@ def setup_chat_routes(
tool_policy=tool_policy,
owner=_user,
fallbacks=_fallback_candidates,
- workspace=workspace or None,
+ workspace=None,
plan_mode=plan_mode,
approved_plan=approved_plan or None,
):
@@ -1272,8 +1267,7 @@ def setup_chat_routes(
# without waiting on the next streamed chunk.
#
# Normal chat/agent streams keep the DETACHED behavior below: they
- # survive the client closing the tab / navigating away (true
- # terminal-agent semantics). The SSE response just subscribes (replay
+ # survive the client closing the tab / navigating away. The SSE response just subscribes (replay
# buffered output + live); dropping the SSE only removes a subscriber —
# the run keeps going and saves the assistant message on completion
# regardless. Reconnect via /api/chat/resume.
diff --git a/routes/workspace_routes.py b/routes/workspace_routes.py
deleted file mode 100644
index f7b27fbdc..000000000
--- a/routes/workspace_routes.py
+++ /dev/null
@@ -1,56 +0,0 @@
-"""Workspace API — browse server directories to pick a tool workspace folder."""
-import os
-from fastapi import APIRouter, Request, HTTPException, Query
-
-from src.auth_helpers import get_current_user
-from src.tool_security import owner_is_admin_or_single_user
-
-
-def setup_workspace_routes():
- router = APIRouter(prefix="/api/workspace", tags=["workspace"])
-
- @router.get("/browse")
- def browse(request: Request, path: str = Query(default="")):
- """List subdirectories of `path` (default: home) so the UI can navigate
- the server filesystem and pick a workspace folder. Directories only.
-
- ADMIN-ONLY: this enumerates the server filesystem, so it is gated the
- same way the file/shell tools are (read_file/write_file/bash are in
- NON_ADMIN_BLOCKED_TOOLS). A non-admin who can't use those tools must not
- be able to map the host's directory tree either.
- """
- owner = get_current_user(request)
- if not owner_is_admin_or_single_user(owner):
- raise HTTPException(status_code=403, detail="Workspace browsing is admin-only")
-
- # Resolve symlinks so the reported path is canonical and the UI navigates
- # real directories (defends against symlink games in displayed paths).
- target = os.path.realpath(os.path.expanduser(path.strip() or "~"))
- if not os.path.isdir(target):
- target = os.path.realpath(os.path.expanduser("~"))
-
- dirs = []
- try:
- with os.scandir(target) as it:
- for entry in it:
- try:
- # Don't follow symlinks when classifying — a symlinked
- # dir is skipped rather than letting the browser wander
- # off via a link. Hidden entries are omitted.
- if entry.is_dir(follow_symlinks=False) and not entry.name.startswith("."):
- # Build the child path server-side with os.path.join
- # so it's correct on Windows (backslashes) and Linux.
- dirs.append({"name": entry.name, "path": os.path.join(target, entry.name)})
- except OSError:
- continue
- except (PermissionError, OSError):
- dirs = []
-
- parent = os.path.dirname(target)
- return {
- "path": target,
- "parent": parent if parent and parent != target else None,
- "dirs": sorted(dirs, key=lambda d: d["name"].lower()),
- }
-
- return router
diff --git a/static/app.js b/static/app.js
index 20ef1f705..c75070bf2 100644
--- a/static/app.js
+++ b/static/app.js
@@ -4,7 +4,6 @@
// ============================================
import Storage from './js/storage.js';
import uiModule from './js/ui.js';
-import workspaceModule from './js/workspace.js';
import fileHandlerModule from './js/fileHandler.js';
import modelsModule from './js/models.js';
import ragModule from './js/rag.js';
@@ -1702,9 +1701,6 @@ function initializeEventListeners() {
}
setupToggle('web-toggle-btn', 'web-toggle', 'web');
setupToggle('bash-toggle-btn', 'bash-toggle', 'bash');
- try { workspaceModule.initWorkspace(); } catch (_) {}
-
- try { workspaceModule.initWorkspace(); } catch (_) {}
// Document editor toggle (special: uses module panel, not a checkbox)
const overflowDocBtn = el('overflow-doc-btn');
diff --git a/static/index.html b/static/index.html
index ae3092659..4ca33c072 100644
--- a/static/index.html
+++ b/static/index.html
@@ -1040,13 +1040,6 @@
RAG
-
-