Skip to content

About

Local-first AI coding editor on Eclipse Theia: an agent that lives inside the editor as a native panel, with your code, the editor, and the model all on your machine.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Undisclosed

Screenshot 2026-10-09 at 14 56 48

An on-device AI coding editor with enforced guardrails and receipts. At its core it is a customized Eclipse Theia editor with an AI coding agent built in as a native panel that lives inside the editor - the agent is not docked next to a separate window the way it works in other tools. It plans, reads, writes and runs code against your actual workspace, right where you edit.

Local by default: your code, the editor, and the agent "brain" (the model orchestration) run on your machine. Point it at a model on your own machine and nothing leaves it. Or bring your own key (BYOK) for a cloud model you trust, and only the prompt the agent assembled goes there: the request, the conversation context, and the file contents it read.

The project began as two decoupled halves: the agent capability is from Eigent, while the editor shell + on-device packaging here are our own. Both are fused here into a single editor-first product - the "Antigravity" model.


Demo

undisclosed-demo.mp4

Watch the 33-second demo (1920x1080, mp4) if your viewer doesn't play it inline. The demo video was cut, captioned and rendered inside Undisclosed itself, with the agent driving the HyperFrames pipeline. The task is the dispatchkit work sample: three bugs in a three-file webhook delivery worker. The agent reads the tests and the modules, names the three bugs, fixes them in place, then runs pytest and all thirteen pass. It shows the loop end to end, not the ceiling.

Vertical cut for TikTok, Reels and Shorts (1080x1920, 32s): undisclosed-demo-9x16.mp4.


Highlights

  • Agent-native editor shell built on Eclipse Theia (not a fork of VS Code), 100% open-source (Apache-2.0). The agent panel (a React app) is served and mounted as a first-class dock widget by a Theia extension.
  • It can actually touch your workspace: terminal, file read/write, git, search, debugging, browser + screenshots, web search - via selectable "hands". The agent plans in your editor and executes changes you can see and review.
  • Runs on-device. The "brain" is a local FastAPI (uvicorn) agent backend, bound to 127.0.0.1:5001 by default. No SaaS runtime, no vendor cloud. Model calls use the keys you configure (OpenAI, Gemini, and more).
  • Verifiable controls, not a promise: an approval gate for risky tools, on by default (risky calls wait for your approval; always-approve is opt-in), a memory switch with Clear, opt-in debug dumps, and localhost-only networking by default. See Privacy and control.
  • Checks before "done": typecheck + lint, a deterministic diff review, and tests paired with a coverage-gap plan. See Reliability over tool count.
  • Storybook + hot reload for fast agent-UI iteration, separate from the slow full editor rebuild.
  • Installable desktop app (Electron + frozen Python brain) for macOS / Windows / Linux, plus a ready-to-package release pipeline.
  • Extensible: Monaco/Monarch custom languages, VS Code settings + extension import, git extras, and a skill system for the agent (20 skills bundled, 6 enabled on a new install, the rest one toggle away).

Where it fits

The shape question changed. Cline now ships a CLI and a desktop app, and OpenCode has a desktop beta, so "extension vs terminal" no longer separates the field. What still separates it is whether the editor is open, whether the model can run on your machine, and whether a change reaches you as a diff you approve.

What it is today What you own, and what you give up
Cline Extension for VS Code and JetBrains, a CLI, and a desktop agent app Open source, local models or your own key. In the editor it is still a guest inside the host's extension API
OpenCode Terminal UI, plus a desktop app in beta Open source, local models or your own key. Fast and scriptable; diffs are reviewed in the TUI or by opening an editor
Cursor Closed-source VS Code fork Polished and account-bound. The vendor is in the model path by default, and the code that reads your code is not open
Undisclosed The editor itself: Theia with the agent docked Apache-2.0, a local model or your own key, and diffs, terminal, git and the test receipt in one window. Newer and smaller, and the macOS build is unsigned

Checked 20 October 2026 against each project's own README. This category moves fast; if a row is out of date, open an issue and we will correct it.


What's in the box

Two local processes keep the editor and the agent separate:

Process What it is Listens on
Editor Theia + the undisclosed-* extensions + the embedded agent UI :3000 (dev, browser)
Brain FastAPI / uvicorn agent backend (model routing, toolkits/hands, memory, SSE streaming) :5001

The agent UI (agent-ui/) is a React + Vite app. A self-contained UMD module is built and copied into the undisclosed-agent Theia extension, which serves it and mounts it in the right-side panel. In development the brain runs as its own process (scripts/brain.sh); in the packaged desktop app it is frozen with PyInstaller and auto-launched alongside Electron.

┌───────────────────────────────────────────────┐     ┌───────────────────────────────┐
│  Editor surface (Theia + undisclosed-agent)   │     │   Brain  (FastAPI, :5001)      │
│   · editor / terminal / git / file explorer   │     │   · model routing/streaming    │
│   · agent panel = native dock widget (React)  │◄───►│   · hands: shell, files, git,  │
│      ┌──────────┐                             │/api │     browser, search, skills    │
│      │  Agent UI │  ── /api & SSE ─────────────┴────►│   · memory (vector) + history │
└──────┴──────────┴─────────────────────────────┘     └───────────────────────────────┘
       In dev the browser proxies /api on :3000 → brain; in the packaged app the
       Theia/Electron backend proxies /api straight to the bundled brain.

Privacy and control

"Local-first" is only worth something if you can check it. These are the concrete answers for this build, with the code that implements each one.

Does the agent send my source code to a cloud provider?

Only when you choose a cloud model.

  • Local model: if the model runs on your machine (Ollama, LM Studio, Hugging Face, or any OpenAI-compatible server on localhost), the model call stays on the machine. No Undisclosed server is in the path. Local coding is still behind cloud on long, multi-file work, so use the largest coding model your machine can hold (14B and up if it fits) and expect to review more of the diff.
  • Cloud model (BYOK): the provider you configured receives the request the agent assembled: the prompt, the conversation context, file contents the agent read, and tool results it is reasoning over. The editor and brain still run locally; the key goes from this machine to that provider, not through us.
  • Other hands have their own network paths: web search, the CDP browser, GitHub, Figma, Notion, Slack, Google Workspace and similar toolkits talk to their own services when you connect them. Each one stays unavailable until its credentials are configured.

There is no Undisclosed account and we do not ship an analytics SDK in the app. The minimum network requirement is the model endpoint you configure.

What is stored, and where?

Everything is on disk under your home directory:

Path What it holds
~/.undisclosed/turns/ conversation turns (turn_<id>.json): messages, tool activity summaries, token counts
~/.undisclosed/memory/ the memory tree: facts, conversation events, tool events, run summaries, and the local vector store
~/.undisclosed/logs/brain.log packaged brain stdout/stderr, rotated at 16 MB
~/.undisclosed/debug/<task>.log full turn dumps, only when UNDISCLOSED_DEBUG=1: prompt, tool calls with arguments and results, model response
~/eigent/ (falls back to ~/.undisclosed/) per-task run artifacts and the working files the brain writes

Memory is on by default. The panel's Memory screen has a "Remember across conversations" switch and a Clear button (GET /memory/status, DELETE /memory). Turning it off stops new facts for new tasks; Clear removes the stored facts. Debug dumps are off unless you set the flag.

Can it run a destructive shell command without approval?

No, not by default. Ask is the default mode: mutating calls wait for an explicit approval in the panel before they execute. Auto (always approve) is opt-in from the panel's mode menu or Settings > Privacy, and the choice is remembered per user.

In Ask mode these wait for approval:

  • shell_exec (terminal)
  • write_to_file and apply_patch
  • execute_code (worker agents)
  • git writes (git_add, git_commit, git_push, git_checkout, git_stash, git_create_branch)
  • GitHub writes (issues, pull requests, comments, branches)
  • MongoDB's write-capable mongo_eval escape hatch

The prompt names the tool, its arguments and the workspace (built by _describe_call in governance_toolkit.py). Denying it, or not answering within five minutes, returns a governance rejection to the model instead of running the action. Read-only calls, including read-only shell commands, are not gated.

Commands are classified per segment by command_policy.py. A hard-blocked command (rm, sudo, dd, mkfs, find -delete, xargs rm) is refused in both modes and approval cannot override it. The destructive tier (git push, git reset, git clean, GitHub writes) prompts in both modes, Auto included. A compound command is judged by its riskiest part. The mode is sent with every turn as toolkit_config.governance.mode; the gate wiring is in toolkit_assembler.py and the UI side in chatStore.ts.

This is static analysis of the command line, not a sandbox. Two limits follow from that: the contents of a script file are not read before it runs, and inline code that can hand the command to another process (os.system, subprocess, child_process, __import__) is tier 2, so it prompts in every mode. A literal delete target outside the workspace is tier 3; a target the classifier cannot resolve statically is tier 2.

What about integrations that can modify external resources?

Integrations are credential-gated: the toolkit is only assembled when its token, OAuth grant, or MCP server is present. Ask mode (the default) gates local file writes, the MongoDB escape hatch and git add/commit. GitHub writes and the destructive git actions (push, reset, clean) prompt in every mode, Auto included.

Can I see what it is about to do?

  • Ask mode (the default) shows the exact pending call before it runs.
  • The agent plans in the panel, and file changes show up as diffs you can review in the panel and in git.
  • UNDISCLOSED_DEBUG=1 records the outgoing prompt and every tool call/result. To see the actual network traffic, run the brain under a proxy or watch the loopback interface; it binds 127.0.0.1:5001 by default (Docker uses 0.0.0.0).

Reliability over tool count

An agent that supports fifteen tools and frequently makes the wrong change is less useful than one that supports five and finishes predictably. Undisclosed ships a verification loop instead of a longer tool list.

The developer prompt makes these steps mandatory before a change is called done (see <verification_workflow> in prompt.py):

  1. Typecheck and lint. run_checks detects the project's typecheck and lint commands and runs both (code_review_toolkit.py).
  2. Review the diff. review_diff scans the changed lines with a deterministic ruleset and returns line-anchored findings (file:line [severity] message -> suggestion). No extra model call, no external reviewer service.
  3. Test with coverage. run_tests runs the suite with coverage and appends a coverage_gap_plan; the agent has to state which gaps it is closing or why a gap is out of scope (test_runner_toolkit.py).

The panel keeps the receipts: a file diff, a test-run card, and the verification summary for the turn. The cards are parsed from the turn's tool activity and command output, not from the model's write-up, so the count on the test card is what the runner printed.

We judge the agent on real tasks, not benchmark theatre: do the tests pass, does the diff match the request, how often did it need intervention, and how much manual correction did the result need. Those are the numbers that decide whether a release is good. We do not publish a tool-count score.

That evaluation currently runs on this repo and on Angler. There is no public leaderboard yet. When there is one, it will report those four numbers.


Projects built with Undisclosed

The product is early, so this list is short on purpose. It grows as more work ships.

  • Undisclosed itself: the editor, the agent panel, the brain, this README, and the demo video rendered inside it.
  • Angler is a macOS menu-bar app that turns the flat Mac desktop into a surface with depth as you tilt the laptop lid. The app and its site were built with Undisclosed.

If you build something with it, open an issue with a link and we will add it.


Prerequisites

Tool Version Why
Node 24 (use nvm use 24) Theia 1.76 requires Node >= 24; native modules are built for it
Python 3.11 The brain; also node-gyp needs distutils (removed in 3.12) when packaging native modules
uv latest Provisions the brain's venv from brain/pyproject.toml

An .nvmrc pins Node 24. Copy .env.sample → .env for any model keys / tuning. Every variable is optional - the app runs with sane defaults (see Environment variables).


Quick start (development)

From a fresh clone you need three things: Node 24, Python 3.11, and uv (see Prerequisites). You also need a workspace folder open for the agent to act on.

0. Point at a model (optional). Copy the sample env and add any keys you want. Every variable is optional and the app runs with sane defaults:

cp .env.sample .env

1. Start the brain (agent backend, :5001):

./scripts/brain.sh setup      # once - provisions brain/.venv via uv
./scripts/brain.sh start      # start (or: restart | stop | logs | status)

2. Install, build and start the editor (Theia, :3000):

nvm use 24                    # Node 24 (.nvmrc); Theia 1.76 requires it
npm install                   # root deps (Theia + the file: packages/*)
npm run build                 # compile packages/* then bundle the editor (dev mode)
npm start                     # -> http://127.0.0.1:3000

npm run build first compiles the four packages/* Theia extensions with tsc (build:packages: undisclosed-agent, undisclosed-git, undisclosed-import, undisclosed-languages) and then bundles the editor with Theia 1.76's esbuild builder. Both matter: the extensions' lib/ output is gitignored, so a fresh clone has nothing for Theia to load until you build them. Open http://127.0.0.1:3000, open a workspace folder, add a model in the agent panel's Models UI, then talk to the agent.

The agent panel's UI bundle is committed to the repo (packages/undisclosed-agent/assets/agent-embed/), so a normal dev build needs nothing from agent-ui/. Rebuild it only when you are editing the agent UI (below).

Prefer one command? ./scripts/dev.sh start brings up the brain and the editor together, and ./scripts/dev.sh rebuild does a full refresh (it pins Node 24 for you).

Working on the agent UI

The editor's agent panel is agent-ui/ (React + Vite). For component work use Storybook (hot reload, no editor rebuild):

cd agent-ui && npm install && npm run storybook   # -> http://localhost:6006

To rebuild the embed bundle that the editor ships, and refresh the committed copy:

npm run build:agent-ui   # vite build -> packages/undisclosed-agent/assets/agent-embed/

agent-ui/ uses a locally patched @stackframe/react at agent-ui/package/@stackframe/react. That directory is gitignored, so it is not in a fresh clone and npm install inside agent-ui/ fails until it is restored. The committed embed bundle is exactly why a normal editor dev build does not need it. scripts/release.sh runs npm run build:agent-ui as its pre-tag gate on a machine that has the patch.

Debugging

Set UNDISCLOSED_DEBUG=1 (in .env) to dump each turn's prompt, tool calls, model response and streamed chunks to the brain log and ~/.undisclosed/debug/<task>.log. In the browser devtools console, localStorage.setItem('undisclosed_debug','1') mirrors this on the frontend.


Try a packaged desktop app

apps/desktop/ is the Electron Theia shell that bundles both the editor and the brain (frozen with PyInstaller) into one installable app and auto-launches the brain on open - no separate processes, no browser tab. Build artifacts land under apps/desktop/dist/ (e.g. a .dmg for macOS).

The prebuilt images are unsigned (self-install). On first open on macOS: right-click the app → Open, or xattr -cr /Applications/Undisclosed.app.

Building the installer locally

One command freezes the brain, builds the agent UI, and packages:

nvm use 20
export npm_config_python="$PWD/brain/.venv/bin/python"   # node-gyp needs Python 3.11
npm run desktop:install                                  # once, for apps/desktop node_modules
npm --prefix apps/desktop run rebuild                    # native modules → Electron ABI
npm run dist:mac        # also dist:win / dist:linux → apps/desktop/dist/*

Full details + troubleshooting: docs/PACKAGING.md.


Cut a release

Releases are tag-driven: CI builds installers on native runners and attaches them to a GitHub Release.

Before tagging, reproduce CI locally so a broken lockfile / plugin download / build fails here instead of burning a CI run:

./scripts/preflight-ci.sh --full            # real install + rebuild + build + plugin download
./scripts/preflight-ci.sh --full --package  # ...and package (electron-builder --dir)
./scripts/preflight-ci.sh                   # fast: workflow YAML + lockfile sync + syntax only

--full runs the real install/build on THIS machine (host platform only), so it can't see Windows/Linux-only problems. Then release:

npm run release [patch|minor|major]

This gates on npm run build:agent-ui, bumps the version, commits, tags vX.Y.Z, and pushes. The workflow (.github/workflows/release.yml) then builds on native runners (Linux AppImage/deb, Windows nsis, and macOS for both Apple Silicon and Intel via macos-15 + macos-15-intel) and attaches the installers to the Release. macOS builds are unsigned: fine for self-install (right-click -> Open), but they warn under Gatekeeper until Developer ID signing/notarization is configured. The app id / product name / copyright used by packaging live in apps/desktop/electron-builder.yml.


Repository layout

agent-ui/            Agent UI (React + Vite) → built to a self-contained UMD bundle
brain/               FastAPI agent backend ("the brain"); uv + pyproject.toml;
                     the /api surface (providers, spaces, history, chat)
packages/
  undisclosed-agent/       Theia extension: hosts the agent dock panel, the proxy,
                           layout & git extras; brain launcher (desktop)
  undisclosed-git/         SCM fixes: staged changes open editable (save works),
                           plus inline diff / commit-widget styling
  undisclosed-languages/   Monaco/Monarch language support + native media preview
  undisclosed-import/      VS Code settings/extensions import
  agent-net/               Framework-agnostic transport (fetch + SSE) shared by the
                           agent widget and the Electron app
apps/desktop/        Electron Theia app (packaging target; electron-builder.yml)
scripts/             brain.sh, build-brain.sh (PyInstaller), release.sh, dev.sh
docs/                PACKAGING.md, design & dev notes (assistant_message_persistence.md)
docker-compose.yml   Optional containerized brain (dev convenience)

Environment variables

Configuration is read from .env (repo root) or brain/.env - both are loaded by the brain at startup and gitignored. Precedence: these files use override=True, so they win over shell exports (once a var is here it is the source of truth). See .env.sample for the full annotated list.

Notable groups:

  • Server: UNDISCLOSED_BRAIN_PORT (default 5001), UNDISCLOSED_BRAIN_HOST, UNDISCLOSED_PROXY_TARGET (override where the Theia backend sends /api calls).
  • Agent behavior: per-step timeout, memory window, extended thinking (effort / budget), tool-RAG (expose only the tools relevant to a turn) and deferred/lazy browser + screenshot toolkits, debug mode (UNDISCLOSED_DEBUG), storage overrides (~/.undisclosed/).
  • Model / LLM keys (BYOK): OPENAI_API_KEY, GEMINI_API_KEY, GOOGLE_API_KEY. Most provider/model setup is configurable inside the app's Models UI; these vars are read directly where a toolkit needs a key without a UI-configured provider.
  • Design/dev integrations (each enables its corresponding agent toolkit only when set): FIGMA_ACCESS_TOKEN, GITHUB_ACCESS_TOKEN, MONGO_URL. The GitHub toolkit (needs the PyGithub package declared in brain/pyproject.toml) covers issue create/update/close/reopen, issue and PR comments, PR create/merge/close, branch create/list, repository listing, and cross-repo issue search. The MongoDB toolkit is a read-only query surface (find/count/distinct/aggregate plus a write-guarded escape hatch) run through the mongosh CLI; it redacts the connection string from every result and error, defaults to the dev database (MONGO_DB to override), and never enables without a connection string.
  • Web search (any one enables web search): Google PSE (key+engine id), Tavily, Brave, Exa, Linkup, Bocha.
  • Productivity/comms: Notion, Slack, Google Workspace (Gmail/Calendar/Drive OAuth + token paths), social (Twitter/X, LinkedIn, Reddit, WhatsApp), Lark/Feishu.
  • Browser (CDP): point the brain at a running Chrome via UNDISCLOSED_CDP_URL, or override the launched browser / command timeout.

Frontend (build-time) vars live in agent-ui/.env, not the root .env - Vite reads only vars prefixed VITE_ into the browser bundle (VITE_BRAIN_ENDPOINT, VITE_BASE_URL, VITE_PROXY_URL, VITE_APP_VERSION).


Troubleshooting

Symptom Likely cause / fix
Editor won't build ("Python 3.12 / distutils") Use Node 24 + Python 3.11; nvm use 24, and point node-gyp at the venv python before packaging (export npm_config_python=...).
Agent panel shows a proxy/api error when adding a model The /api proxy must target the brain (:5001), not a legacy :3001 cloud proxy. In dev run via scripts/dev.sh (sets the target) or in .env set UNDISCLOSED_PROXY_TARGET=http://127.0.0.1:5001.
Brain won't start Ctrl+C a prior uv run ... may leave uv_installing.lock/uv_installed.lock in brain/ - delete them, then ./scripts/brain.sh setup.
Packaged app is unsigned on macOS Right-click → Open, or xattr -cr /Applications/Undisclosed.app. See docs/PACKAGING.md for signing/notarization.
CI hangs forever at collecting extension dependencies theia download:plugins talks to open-vsx over sockets with no timeout, and its "already downloaded - skipping" lines don't stop it running (or hanging) anyway. CI uses scripts/ci-download-plugins.sh: it skips the tool entirely when the pinned plugins are already present, bounds each attempt with a hard cap, and stops the tool as soon as the plugins verify. Reproduce locally with bash scripts/ci-download-plugins.sh.

On the browser side you can run the whole stack from one terminal with ./scripts/dev.sh, which manages the brain + editor together (and defaults the proxy target to :5001).


License

Apache-2.0. The full text is in LICENSE, with required attributions in NOTICE.

Portions © Eigent.ai, used under Apache-2.0. The on-device editor shell, packaging and new features © Simon Ugorji. Eclipse Theia, bundled VS Code extensions, skills and other dependencies remain under their own licenses.

About

Local-first AI coding editor on Eclipse Theia: an agent that lives inside the editor as a native panel, with your code, the editor, and the model all on your machine.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages