v2.12.1
Jul 18, 2026
Grok reaches parity with the other cloud agents, the Cortex knowledge base grows a real management surface, and the mobile knowledge experience is rebuilt for phones.
opendray updateReadable release notes from GitHub, designed for humans.
Jul 18, 2026
Grok reaches parity with the other cloud agents, the Cortex knowledge base grows a real management surface, and the mobile knowledge experience is rebuilt for phones.
opendray updateGrok reaches parity with the other cloud agents, the Cortex knowledge base grows a real management surface, and the mobile knowledge experience is rebuilt for phones.
doc_read, cross-layer recall) just like Claude / Codex
/ Antigravity — its spawn folder is marked trusted so grok actually starts
the injected memory server instead of silently skipping it. Grok and OpenCode
are also selectable in Discuss with AI and as Memory Worker agent
providers, and creating a grok session now offers the Bypass permissions /
YOLO toggle (--always-approve) the other agents already had.kb_* page's title,
one-line description, nature (foundational / emergent) and inject flag were
locked in at creation; they are now editable in place (web + mobile) on every
page except the classic four — including seeded pages like Integrations, so
you can flip a page between full-inject and on-demand retrieval.@mentionable
on the next turn with the full thread as context; a removed one stops
replying while its past messages stay.kb_* docs; the KB tab is now a grouped, searchable list (Foundational /
Emergent) that grows gracefully, and tapping a page opens a full-screen
reader/editor with its actions in an AppBar overflow menu. New page and the
Librarian move onto a FAB.<cwd>/.grok/config.toml, but grok refuses to start
repo-local MCP servers in an untrusted folder as a supply-chain guard, so the
server was configured but never started. opendray now trusts the operator's
own spawn folder (--trust), matching the other CLIs.Grok reaches parity with the other cloud agents, the Cortex knowledge base grows a real management surface, and the mobile knowledge experience is rebuilt for phones.
doc_read, cross-layer recall) just like Claude / Codex
/ Antigravity — its spawn folder is marked trusted so grok actually starts
the injected memory server instead of silently skipping it. Grok and OpenCode
are also selectable in Discuss with AI and as Memory Worker agent
providers, and creating a grok session now offers the Bypass permissions /
YOLO toggle (--always-approve) the other agents already had.kb_* page's title,
one-line description, nature (foundational / emergent) and inject flag were
locked in at creation; they are now editable in place (web + mobile) on every
page except the classic four — including seeded pages like Integrations, so
you can flip a page between full-inject and on-demand retrieval.@mentionable
on the next turn with the full thread as context; a removed one stops
replying while its past messages stay.kb_* docs; the KB tab is now a grouped, searchable list (Foundational /
Emergent) that grows gracefully, and tapping a page opens a full-screen
reader/editor with its actions in an AppBar overflow menu. New page and the
Librarian move onto a FAB.<cwd>/.grok/config.toml, but grok refuses to start
repo-local MCP servers in an untrusted folder as a supply-chain guard, so the
server was configured but never started. opendray now trusts the operator's
own spawn folder (--trust), matching the other CLIs.Added Round Table — a cross vendor AI group chat (experimental). Seat several providers (Claude / Codex / Antigravity / Grok / OpenCode) plus the operator in one shared thread; @mention who should reply (or @all) and eac
@all) and each member
answers in character after reading the whole conversation, so heterogeneous
foundation-model families react to each other in seat order. Summarize the
discussion on demand, or turn it into a role-assigned execution plan —
each step runs as a real session in a shared project (bind the project after
the fact if you started without one). Hand the whole thread off to a
working session to do the actual code changes. A chat can be closed and
reopened (close keeps the thread, just stops new messages). Available on
both the web admin and the mobile app — where Round Table gets its own
bottom-nav tab, per-agent bubble colours, and labelled action menus.
Fully self-contained and rollback-able (internal/roundtable/ROLLBACK.md).group-hover) or keyboard focus. Tailwind v4 gates group-hover behind
@media (hover: hover), so on a touch device — which can neither hover nor
focus a row — the icon stayed at opacity-0 and was impossible to tap. It
now pins visible under @media (hover: none), so touch users get a
permanently-shown download control while pointer users keep the clean
hover-reveal. (Follow-up to the v2.11.6 positioning fix, which addressed
where the icon sits but not whether it ever appears without a mouse.)[mcp_servers."…"] TOML
table and died with duplicate key at startup — before printing a byte, so
the session flipped straight to the read-only "[buffer unavailable]" view —
while Claude's map-based renderer silently dropped one of them. renderMCP
now rejects a duplicate name up front (for every provider, before any config
file is written), and the Plugins create/update endpoints return 409 when
a new or edited server would reuse a name already taken by a different id.
Grok's manifest gap and this collision are unrelated; a stray second Notion
entry sharing the name Notion API is what exposed it.grok manifest carried an empty npmPackage, and the whole update path is
npm-gated: CheckUpdate returned early (no latest version, no
"update available" flag) and Update hard-errored with "not updatable via
npm". Grok is published as @xai-official/grok (maintainer
[email protected]), so the manifest now names it.curl -fsSL https://x.ai/cli/install.sh | bash) drops
a symlink into the npm bin dir that npm does not own, and npm refuses to
clobber it — EEXIST: file already exists. Simply naming the package would
therefore have shipped a dashboard that advertises an update behind a button
that always fails. Update now preflights the bin path: an unmanaged
symlink is cleared so npm can take ownership (and the update output tells
the operator exactly which link was replaced), while a regular file is
never deleted — it is reported instead, mirroring the existing
ErrUpdatePrefixReadonly preflight. Grok's install note now recommends
npm install -g @xai-official/grok.@all) and each member
answers in character after reading the whole conversation, so heterogeneous
foundation-model families react to each other in seat order. Summarize the
discussion on demand, or turn it into a role-assigned execution plan —
each step runs as a real session in a shared project (bind the project after
the fact if you started without one). Hand the whole thread off to a
working session to do the actual code changes. A chat can be closed and
reopened (close keeps the thread, just stops new messages). Available on
both the web admin and the mobile app — where Round Table gets its own
bottom-nav tab, per-agent bubble colours, and labelled action menus.
Fully self-contained and rollback-able (internal/roundtable/ROLLBACK.md).group-hover) or keyboard focus. Tailwind v4 gates group-hover behind
@media (hover: hover), so on a touch device — which can neither hover nor
focus a row — the icon stayed at opacity-0 and was impossible to tap. It
now pins visible under @media (hover: none), so touch users get a
permanently-shown download control while pointer users keep the clean
hover-reveal. (Follow-up to the v2.11.6 positioning fix, which addressed
where the icon sits but not whether it ever appears without a mouse.)[mcp_servers."…"] TOML
table and died with duplicate key at startup — before printing a byte, so
the session flipped straight to the read-only "[buffer unavailable]" view —
while Claude's map-based renderer silently dropped one of them. renderMCP
now rejects a duplicate name up front (for every provider, before any config
file is written), and the Plugins create/update endpoints return 409 when
a new or edited server would reuse a name already taken by a different id.
Grok's manifest gap and this collision are unrelated; a stray second Notion
entry sharing the name Notion API is what exposed it.grok manifest carried an empty npmPackage, and the whole update path is
npm-gated: CheckUpdate returned early (no latest version, no
"update available" flag) and Update hard-errored with "not updatable via
npm". Grok is published as @xai-official/grok (maintainer
[email protected]), so the manifest now names it.curl -fsSL https://x.ai/cli/install.sh | bash) drops
a symlink into the npm bin dir that npm does not own, and npm refuses to
clobber it — EEXIST: file already exists. Simply naming the package would
therefore have shipped a dashboard that advertises an update behind a button
that always fails. Update now preflights the bin path: an unmanaged
symlink is cleared so npm can take ownership (and the update output tells
the operator exactly which link was replaced), while a regular file is
never deleted — it is reported instead, mirroring the existing
ErrUpdatePrefixReadonly preflight. Grok's install note now recommends
npm install -g @xai-official/grok.Fixed The download icon is reachable again in a deep or long file tree. The session inspector's Files tree renders inside a scroll area whose inner wrapper sizes to its content, so long filenames and deep nesting pushed
Added TUIs follow the opendray theme. A terminal UI picks a light/dark palette by asking the terminal — via the OSC 11 background query (which xterm.js already answered) or the COLORFGBG environment variable, which opendray never set.
COLORFGBG environment variable, which
opendray never set. So a CLI that reads the environment (Grok's
theme = "auto", vim, tmux, …) had no way to know the operator was in
light mode and always defaulted to dark. opendray now stamps the client's
applied theme on session create and advertises it at spawn via
COLORFGBG. Optional and backward-compatible: no theme advertises
nothing and the CLI keeps its own default, and an explicit COLORFGBG
already in the environment still wins. (#446)npm install -g on the HTTP request context, so a client
disconnect (browser closed, proxy timeout) cancelled it and SIGKILLed npm
mid-install. A half-killed npm leaves a partial global tree behind — a
stale .<pkg>-XXXXXX temp dir — after which every later install fails
with ENOTEMPTY, permanently wedging updates for that CLI (a codex update
stayed broken for a week this way, and left a CLI whose platform binary
never landed, so its sessions failed too). The install is now detached
from the caller's cancellation. (#445)PATH but won't run, opendray used to fall back
to showing the manifest version — rendering a CLI that can't even launch
as perfectly fine. It now reports "Installed but not runnable" with the
CLI's own error, and a failed update surfaces npm's actual message
(ENOTEMPTY: …) rather than a bare exit status 217. (#445)COLORFGBG environment variable, which
opendray never set. So a CLI that reads the environment (Grok's
theme = "auto", vim, tmux, …) had no way to know the operator was in
light mode and always defaulted to dark. opendray now stamps the client's
applied theme on session create and advertises it at spawn via
COLORFGBG. Optional and backward-compatible: no theme advertises
nothing and the CLI keeps its own default, and an explicit COLORFGBG
already in the environment still wins. (#446)npm install -g on the HTTP request context, so a client
disconnect (browser closed, proxy timeout) cancelled it and SIGKILLed npm
mid-install. A half-killed npm leaves a partial global tree behind — a
stale .<pkg>-XXXXXX temp dir — after which every later install fails
with ENOTEMPTY, permanently wedging updates for that CLI (a codex update
stayed broken for a week this way, and left a CLI whose platform binary
never landed, so its sessions failed too). The install is now detached
from the caller's cancellation. (#445)PATH but won't run, opendray used to fall back
to showing the manifest version — rendering a CLI that can't even launch
as perfectly fine. It now reports "Installed but not runnable" with the
CLI's own error, and a failed update surfaces npm's actual message
(ENOTEMPTY: …) rather than a bare exit status 217. (#445)Added Mobile: Resources section + Updates "what's new" sheet. The mobile app gains the sidebar Resources block and the Updates/"what's new" sheet, reaching parity with the web admin (#433).
~/.gemini/config/mcp_config.json;
opendray's MCP-injection prep parsed it unconditionally and errored with
"provider prepare: parse …/mcp_config.json … unexpected end of JSON
input", blocking every Antigravity session spawn. An empty (or
whitespace-only) file is now treated as "no config yet" rather than a parse
error, on both the mcp_config.json and gemini settings.json surfaces. (#440)~/.gemini/config/mcp_config.json;
opendray's MCP-injection prep parsed it unconditionally and errored with
"provider prepare: parse …/mcp_config.json … unexpected end of JSON
input", blocking every Antigravity session spawn. An empty (or
whitespace-only) file is now treated as "no config yet" rather than a parse
error, on both the mcp_config.json and gemini settings.json surfaces. (#440)Added Session terminal: staged image attachments. Uploading an image to a session (attach button, clipboard paste, or drag and drop) now stages it as a dismissable chip in a tray at the bottom of the terminal instead of
Added Database tool — MySQL, MariaDB and SQLite. The Database tool now connects to MySQL and MariaDB (host/port/username like PostgreSQL; a MySQL "schema" is a database) and SQLite in addition to PostgreSQL.
cwd (a path escaping it via ../ or a
symlink is rejected) and extension loading is disabled. Reads run behind
the same read-only fence on every engine (SQLite via a dedicated
read-only connection pool). All engines are pure-Go drivers
(go-sql-driver/mysql, modernc.org/sqlite), so the binary still
cross-compiles without cgo. Migration 0075 widens the driver
constraint; 0076 reseeds the kb_integrations page.cwd (a path escaping it via ../ or a
symlink is rejected) and extension loading is disabled. Reads run behind
the same read-only fence on every engine (SQLite via a dedicated
read-only connection pool). All engines are pure-Go drivers
(go-sql-driver/mysql, modernc.org/sqlite), so the binary still
cross-compiles without cgo. Migration 0075 widens the driver
constraint; 0076 reseeds the kb_integrations page.Added Mobile parity — Database tool in the session inspector. The mobile app's session inspector gains a Database tab mirroring the web tool: browse schemas and tables, page through rows, insert / update / delete by prim
db:read / db:write scopes and
read-only connections, and reusing the session's cwd for isolation.POST /api/v1/fs/upload,
matching the web files-sidebar upload shipped in v2.11.0 (same
resolveWithinRoot sandbox, auto-rename on name collision).opendray-dbtool MCP now holds a db:signed
key and sends a per-session X-OpenDray-Dbtool-Sig = HMAC(secret, cwd)
header; the gateway rejects a signed-key call whose signature doesn't
match the cwd. An agent that extracts the injected key can no longer
forge another project's cwd — closing the residual the honest-path
check left open. Antigravity (whose MCP config is HOME-global and can't
carry a per-session signature — a Google limitation) and third-party
integration keys keep the plain ?cwd= check via a separate honest-path
key. Migration 0074 reseeds the kb_integrations page.UseNumber, so a 64-bit primary key above
2^53 is no longer rounded through float64 (which could address the
wrong row or match none). Numbers beyond int64 keep their exact string.TableMeta runs its four
catalog queries (columns / PK / indexes / FKs) inside one read-only
transaction, so concurrent DDL can't produce a half-updated view.golang.org/x/crypto 0.50.0 → 0.52.0 (#421) and
golang.org/x/net 0.52.0 → 0.55.0 (#418), pulling transitive x/sys
and x/text updates. Build and vet clean.db:read / db:write scopes and
read-only connections, and reusing the session's cwd for isolation.POST /api/v1/fs/upload,
matching the web files-sidebar upload shipped in v2.11.0 (same
resolveWithinRoot sandbox, auto-rename on name collision).opendray-dbtool MCP now holds a db:signed
key and sends a per-session X-OpenDray-Dbtool-Sig = HMAC(secret, cwd)
header; the gateway rejects a signed-key call whose signature doesn't
match the cwd. An agent that extracts the injected key can no longer
forge another project's cwd — closing the residual the honest-path
check left open. Antigravity (whose MCP config is HOME-global and can't
carry a per-session signature — a Google limitation) and third-party
integration keys keep the plain ?cwd= check via a separate honest-path
key. Migration 0074 reseeds the kb_integrations page.UseNumber, so a 64-bit primary key above
2^53 is no longer rounded through float64 (which could address the
wrong row or match none). Numbers beyond int64 keep their exact string.TableMeta runs its four
catalog queries (columns / PK / indexes / FKs) inside one read-only
transaction, so concurrent DDL can't produce a half-updated view.golang.org/x/crypto 0.50.0 → 0.52.0 (#421) and
golang.org/x/net 0.52.0 → 0.55.0 (#418), pulling transitive x/sys
and x/text updates. Build and vet clean.Added Upload files & folders into a session from the files sidebar. The session inspector's files panel can now create folders and upload files or whole folders (recursively, preserving the subtree) via an upload button
resolveWithinRoot sandbox the download/zip endpoints
use — path traversal and symlinked-intermediate escapes are rejected.
Conflicting names auto-rename (name-1.ext) instead of overwriting what
the session produced. New admin-only endpoint POST /api/v1/fs/upload
on the existing /fs group; the tree refreshes to show new files
(including renames). (#420)opendray-dbtool
MCP server (db_connections_list / db_schema / db_table_data /
db_query / db_execute) so agent sessions can query and mutate a
project's database directly. PostgreSQL only for now (a driver interface
reserves MySQL/SQLite). Connection passwords are encrypted at rest with
the same field cipher as channel/git-host secrets and are never returned
by any read endpoint. Two new scopes — db:read (browse + read-only
SQL) and db:write (row CRUD + write/DDL) — gate integration access;
registering a connection stays admin-only (an integration can never
point opendray at a new host). Reads run inside a server-side READ ONLY
transaction with a statement timeout, and per-connection read_only
refuses every write regardless of scope. The dbtool MCP is withheld from
origin=integration sessions, matching memory isolation. Configurable
via [dbtool] (enabled by default; the feature is inert until a
connection is registered). Migrations 0072 (schema) and 0073
(kb_integrations reseed).memory_search
and memory_load_context, so callers see the merged form rather than
near-duplicate rows. (#414)project_search moved from admin-only to dual-auth + memory:read
scope, so integrations can search project memory. (#413)resolveWithinRoot sandbox the download/zip endpoints
use — path traversal and symlinked-intermediate escapes are rejected.
Conflicting names auto-rename (name-1.ext) instead of overwriting what
the session produced. New admin-only endpoint POST /api/v1/fs/upload
on the existing /fs group; the tree refreshes to show new files
(including renames). (#420)opendray-dbtool
MCP server (db_connections_list / db_schema / db_table_data /
db_query / db_execute) so agent sessions can query and mutate a
project's database directly. PostgreSQL only for now (a driver interface
reserves MySQL/SQLite). Connection passwords are encrypted at rest with
the same field cipher as channel/git-host secrets and are never returned
by any read endpoint. Two new scopes — db:read (browse + read-only
SQL) and db:write (row CRUD + write/DDL) — gate integration access;
registering a connection stays admin-only (an integration can never
point opendray at a new host). Reads run inside a server-side READ ONLY
transaction with a statement timeout, and per-connection read_only
refuses every write regardless of scope. The dbtool MCP is withheld from
origin=integration sessions, matching memory isolation. Configurable
via [dbtool] (enabled by default; the feature is inert until a
connection is registered). Migrations 0072 (schema) and 0073
(kb_integrations reseed).memory_search
and memory_load_context, so callers see the merged form rather than
near-duplicate rows. (#414)project_search moved from admin-only to dual-auth + memory:read
scope, so integrations can search project memory. (#413)Added MCP servers reach Grok. Grok Build sessions now receive opendray's enabled MCP registry (HashiCorp Vault, etc.), per provider mcp servers, integration scoped servers, and the opendray memory server — the same injec
mcp_servers,
integration-scoped servers, and the opendray-memory server — the same
injection every other MCP-capable provider gets. opendray writes them
into the project-scoped <cwd>/.grok/config.toml [mcp_servers] table,
which Grok union-merges with your global ~/.grok/config.toml (your
personal servers are untouched). Previously Grok shipped with MCP
injection disabled, so it could not see Vault or any other shared server
the operator had configured. (#404)kb_* pages) as the authoritative source first, treating any external
mirror (Obsidian vault, wiki) as a secondary fallback used only when the
cortex doesn't cover the topic. Stops agents from grounding infra/DB
process answers in stale external notes when the curated copy is already
in-context. (#406)current_objective body as a dedicated "work to THIS" block (not
just an index entry the agent had to remember to fetch), plus a stronger
proactive-maintenance directive so agents keep current_objective,
the journal, and durable memory current on their own. (#403)doc_read and project_search
can now pull a single heading-section of a large global knowledge page
instead of the whole thing — a kb_integrations lookup drops from
~15K tokens to ~300–1.3K, and search hits carry a
doc_read(slug, section=…) pointer instead of dead-ending on a teaser. (#400)0070, with a one-shot backfill of
historical rows), so the compiler sees the full corpus and its global
playbooks auto-inject at spawn. (#402)mcp_servers,
integration-scoped servers, and the opendray-memory server — the same
injection every other MCP-capable provider gets. opendray writes them
into the project-scoped <cwd>/.grok/config.toml [mcp_servers] table,
which Grok union-merges with your global ~/.grok/config.toml (your
personal servers are untouched). Previously Grok shipped with MCP
injection disabled, so it could not see Vault or any other shared server
the operator had configured. (#404)kb_* pages) as the authoritative source first, treating any external
mirror (Obsidian vault, wiki) as a secondary fallback used only when the
cortex doesn't cover the topic. Stops agents from grounding infra/DB
process answers in stale external notes when the curated copy is already
in-context. (#406)current_objective body as a dedicated "work to THIS" block (not
just an index entry the agent had to remember to fetch), plus a stronger
proactive-maintenance directive so agents keep current_objective,
the journal, and durable memory current on their own. (#403)doc_read and project_search
can now pull a single heading-section of a large global knowledge page
instead of the whole thing — a kb_integrations lookup drops from
~15K tokens to ~300–1.3K, and search hits carry a
doc_read(slug, section=…) pointer instead of dead-ending on a teaser. (#400)0070, with a one-shot backfill of
historical rows), so the compiler sees the full corpus and its global
playbooks auto-inject at spawn. (#402)Added Antigravity multi account. Bind a session to a specific Antigravity (agy) login, switch accounts from the session header, and manage accounts in Providers → Antigravity (discovery + guided HOME=… agy login).
agy) login, switch accounts from the session header, and manage
accounts in Providers → Antigravity (discovery + guided HOME=… agy
login). Accounts are isolated by $HOME — the agy analogue of Claude's
CLAUDE_CONFIG_DIR. Switching accounts keeps the conversation: agy
stores each conversation as a portable per-$HOME SQLite db, so the
switch copies the current conversation into the new account's HOME and
resumes it (--conversation <id>) — you continue the same chat on the
other identity, only the credential/quota changes. Restarting an
antigravity session resumes its conversation too. (#396)grok as a first-class provider
(install with curl -fsSL https://x.ai/cli/install.sh | bash, then
grok login; models grok-build / grok-composer-2.5-fast, bypass via
--always-approve). Resolves exe/model/bypass generically — no per-CLI
adapter code. (#397)/v1, the endpoint serves on the LAN, a chat
model is loaded) instead of OpenCode's opaque [buffer unavailable]. (#398)opendray providers
npm-update list, and the Cortex "discuss with AI" list. Existing Gemini
sessions and on-disk credentials are left untouched, but the provider is
no longer offered to new installs. (#397, #399)agy) login, switch accounts from the session header, and manage
accounts in Providers → Antigravity (discovery + guided HOME=… agy
login). Accounts are isolated by $HOME — the agy analogue of Claude's
CLAUDE_CONFIG_DIR. Switching accounts keeps the conversation: agy
stores each conversation as a portable per-$HOME SQLite db, so the
switch copies the current conversation into the new account's HOME and
resumes it (--conversation <id>) — you continue the same chat on the
other identity, only the credential/quota changes. Restarting an
antigravity session resumes its conversation too. (#396)grok as a first-class provider
(install with curl -fsSL https://x.ai/cli/install.sh | bash, then
grok login; models grok-build / grok-composer-2.5-fast, bypass via
--always-approve). Resolves exe/model/bypass generically — no per-CLI
adapter code. (#397)/v1, the endpoint serves on the LAN, a chat
model is loaded) instead of OpenCode's opaque [buffer unavailable]. (#398)opendray providers
npm-update list, and the Cortex "discuss with AI" list. Existing Gemini
sessions and on-disk credentials are left untouched, but the provider is
no longer offered to new installs. (#397, #399)Fixed Escalating a Cortex discussion now jumps straight into the spawned session and continues on the same CLI + account.
?open=, mobile pushes the
route — instead of only appearing on the next manual list refresh. It also
inherits the conversation's provider / model / Claude-account override
rather than always falling back to Claude. (#390)kb_integrations KB page to match the shipped v2.9.0 contract:
permission_mode (default | bypass) replacing the old
bypass_permissions boolean, the per-principal integration:<id> memory
zone, the enforced providers:write / providers:update scopes, and the
reserved agent_id field. Removes the stale FORTHCOMING framing so any
AI or developer reading it gets the current contract. (#391)?open=, mobile pushes the
route — instead of only appearing on the next manual list refresh. It also
inherits the conversation's provider / model / Claude-account override
rather than always falling back to Claude. (#390)kb_integrations KB page to match the shipped v2.9.0 contract:
permission_mode (default | bypass) replacing the old
bypass_permissions boolean, the per-principal integration:<id> memory
zone, the enforced providers:write / providers:update scopes, and the
reserved agent_id field. Removes the stale FORTHCOMING framing so any
AI or developer reading it gets the current contract. (#391)Operator polish, reconnect stability, memory improvements, and clearer docs.