Skip to content

feat(coda): three-mode framework + coda_interactive, agent/setup fixes, and skill-bundle refresh (Databricks + Flutter/Dart/shadcn) - #67

Open
datasciencemonkey wants to merge 444 commits into
mainfrom
feat/coda-mcp-interactive-handoff
Open

feat(coda): three-mode framework + coda_interactive, agent/setup fixes, and skill-bundle refresh (Databricks + Flutter/Dart/shadcn)#67
datasciencemonkey wants to merge 444 commits into
mainfrom
feat/coda-mcp-interactive-handoff

Conversation

@datasciencemonkey

@datasciencemonkey datasciencemonkey commented May 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two changes that together establish the three-mode framework for the CoDA MCP server:

  1. Narrow coda_run to replay-only URLs — its returned viewer_url now always serves a static transcript from disk, never a live PTY attach. Drops the 5-minute grace machinery introduced in feat: CoDA MCP live session URL — watch hermes execute live + replay #66 (which was actually never wired in production — see commit 193c9a3 for the dead-code analysis).
  2. Add new coda_interactive MCP tool — for human handoff from upstream MCP clients (Genie Code, Claude Desktop, Cursor). Caller passes a Databricks Workspace Git Folder path, optional branch, and a kickoff prompt. Coda exports the file tree, launches the chosen agent (claude default; also hermes/codex/gemini/opencode), auto-types the prompt, and returns a viewer_url for the human to attach to.

The three-mode framework now:

Mode Tool URL semantics PTY lifecycle
1. Direct launch Web UI tab n/a (no external URL) 24h idle / WS-heartbeat extends
2. coda_interactive (new) MCP tool Live attach 24h idle / WS extends
3. coda_run (narrowed) MCP tool Replay only Immediate teardown on hermes exit

Why Workspace Git Folders (not GitHub clone)

coda_interactive materializes project files via the Databricks Workspace API — uses Coda's existing DATABRICKS_TOKEN, no new credentials needed. Trade-off: git history is unavailable inside the session (files-only export). If history matters for a session, the MCP caller can include a git log summary in the prompt string.

Pre-existing security fix bundled

mcp_create_pty_session was stripping only 5 env vars; the HTTP create_session path was stripping NPM_TOKEN, UV_*, and npm_config_//* patterns too. Refactored to share _build_terminal_shell_env (commit ef15ef7). Closes a latent registry-credential leak into MCP-created PTYs.

Subsumes PR #66

PR #66 introduced live-attach + 5-min grace on coda_run. This PR keeps the viewer URL feature but narrows it to replay-only (since live attach moves to coda_interactive). Close #66 if this lands.

Spec + Plan artifacts (in the diff)

  • Todo 1 (Mode 3 narrowing): docs/superpowers/specs/2026-05-28-coda-run-replay-only-design.md + plans/2026-05-28-coda-run-replay-only.md
  • Todo 2 (Mode 2 addition): docs/superpowers/specs/2026-05-28-coda-interactive-mcp-tool-design.md + plans/2026-05-28-coda-interactive-mcp-tool.md
  • Both passed independent critic-agent reviews at spec, plan, and per-task stages.

Test plan

  • Local: 551 passed, 21 skipped — confirmed (pytest tests/ --ignore=tests/e2e)
  • PTY-gated tests skip cleanly on Mac dev environment; should pass on Linux CI/deployed app
  • Manual smoke against deployed CoDA: invoke coda_run from Genie Code; confirm viewer URL is replay-only
  • Manual smoke against deployed CoDA: invoke coda_interactive with a Workspace Git Folder; confirm agent launches in exported project dir with prompt typed
  • Confirm coda_inbox does NOT show interactive sessions; coda_get_result returns nothing for them
  • Verify _build_terminal_shell_env strip on deployed PTY — env | grep -E 'NPM_TOKEN|UV_' should be empty

Follow-up: broadened source contract (commits 326e19a..a555602)

coda_interactive no longer requires the workspace_path to be a Databricks Workspace Git Folder. Any Workspace directory (Git Folder or plain Workspace folder) is accepted. The branch parameter has been removed — callers manage Git Folder branch state themselves before calling.

API change (no shipped consumers — safe):

  • coda_interactive(prompt, workspace_path, branch=..., agent=..., email=...)coda_interactive(prompt, workspace_path, agent=..., email=...)
  • Return shape: "branch" key dropped.

Validation: replaced repos.list + exact-match filter + optional repos.update with a single workspace.get_status call + directory-type check (_is_directory from workspace_export.py). Clean errors for "path not found" and "path is not a directory" — both return before any PTY allocation.

Server-level instructions string rewritten to:

  • Tell callers that plain Workspace folders work.
  • Surface the upload-then-handoff pattern explicitly (workspace.import first if files aren't in the Workspace yet) so an upstream LLM knows the tool doesn't accept inline file payloads.

Quality fix bundled: hoisted the _app_send_input is None guard above PTY creation so an unwired send-hook can no longer orphan a PTY + project dir.

Test delta: −3 / +4. All 13 tests in tests/test_coda_interactive.py pass.

Artifacts:

  • Spec: docs/superpowers/specs/2026-05-28-coda-interactive-broaden-source-design.md
  • Plan: docs/superpowers/plans/2026-05-28-coda-interactive-broaden-source.md
  • Original spec marked as Amended by: for traceability.

Follow-up #2: Workflow protocol + Databricks orientation (commits 6ff6a9b..77321dc)

coda_run now injects two new sections into prompt.txt:

  • CAPABILITIES — tells hermes about the Databricks CLI (pre-authed), the 16 Databricks skills under ~/.claude/skills/, and the DeepWiki / Exa / CoDA MCP servers.
  • WORKFLOW PROTOCOL — imposes a 3-phase pipeline (PLAN → EXECUTE → SYNTHESIZE) with a critique step after each phase (self-review or sub-agent — agent's choice). Max 2 iterations per phase to keep token cost bounded.

New terminal result.json status "info_needed" with a required feedback field gives the calling client a structured iteration loop when the agent is blocked. The existing "needs_approval" status is preserved with explicit disambiguation in the protocol: info_needed = "caller must add context"; needs_approval = "caller must approve a destructive action".

Three upstream-facing surfaces updated so calling LLMs know about the new statuses:

  • coda_inbox counts dict gains info_needed and needs_approval keys.
  • coda_get_result docstring lists all four valid statuses + the new feedback field.
  • FastMCP server-level instructions gain an INFO_NEEDED HANDOFF paragraph teaching upstream LLMs to read feedback and resubmit with previous_session_id.

Flag: coda_run(..., workflow_protocol=True) is the default. Set False to skip both new sections for non-Databricks tasks.

Artifacts:

  • Spec: docs/superpowers/specs/2026-05-28-coda-run-workflow-protocol-design.md
  • Plan: docs/superpowers/plans/2026-05-28-coda-run-workflow-protocol.md

Discipline gates run:

  • Spec critic → APPROVE-WITH-FIXES → 5 fixes applied (counts dict, needs_approval disambig, MCP instructions, canonical skill test, token estimate)
  • Plan critic → APPROVE-WITH-FIXES → 2 fixes applied (correct _write_json stub, expanded regression coverage)
  • Per task (1-4): spec + code-quality reviews, all approvals; minor fixes applied (section_noise dead code, JSON union-syntax placeholder)

Follow-up #3: coda_interactive pulls files in the terminal (fixes empty-session bug)

Bug: Calling coda_interactive launched the agent over an empty directory — the agent had no idea about the user's Workspace files.

Root cause: The MCP server's WorkspaceClient() resolves to the app's service principal (app-167dcd …), which can get_status the user's /Users/<user>/… folder (so the tool reported "launched") but cannot list/export its contents. workspace_export.py swallowed those errors → empty dir + misleading success. Verified: the CoDA terminal's CLI runs as the user (databricks current-user me → the user), and databricks workspace list/export of the folder works as the user via REST.

Fix: Stop exporting server-side. coda_interactive now types cd <project_dir> && databricks workspace export-dir <source> ./<name> && cd <name> into the PTY (authenticated as the user), waits for the pull to settle, then does a server-side filesystem post-check (identity-independent — it stats the local disk the terminal wrote). If files landed it launches the agent and seeds the prompt with a context line naming the source; if nothing landed it returns a real status=error and never launches.

Design highlights:

  • Split waits — wait for the pull to go idle (reliable stabilization), THEN the existing agent-ready wait after launch. Avoids pasting the prompt into a half-initialized agent.
  • databricks workspace export-dir natively handles notebook extensions, replacing the hand-rolled workspace_export.py (deleted, with its tests).
  • _wait_for_agent_ready is now a thin wrapper over a generalized _wait_for_output_stable(pty, max_wait, stability); coda_run is unchanged.
  • New helpers _safe_dirname (sanitized basename, rejects ./..) and _normalize_workspace_path (drops the /Workspace FUSE prefix).
  • MCP instructions wording updated; no more "server-side snapshot".

Tests: test_coda_interactive.py rewritten to the pull contract (pull-first, FS-check failure → no launch, prompt context line, agent matrix); _safe_dirname/_normalize_workspace_path + the wait-wrapper covered in test_mcp_server.py. Suite: 117 passed (only the documented PTY-fd flake fails in multi-file runs; passes in isolation).

Artifacts:

  • Spec: docs/superpowers/specs/2026-05-28-coda-interactive-terminal-pull-design.md
  • Plan: docs/superpowers/plans/2026-05-28-coda-interactive-terminal-pull.md

Discipline gates: brainstorm → design critic (SOUND-WITH-FIXES, folded in) → spec → plan → plan critic (SOUND-WITH-FIXES, folded in) → TDD implementation → final critic (SHIP-WITH-FOLLOWUPS; the one Important finding — .. path-traversal in _safe_dirname — fixed before push).

This branch also merges the latest main (Dependabot deps bump #68: urllib3 2.7.0 / gitpython 3.1.50 / idna 3.16).


Follow-up #4: OpenCode + Codex + Claude-subagent fixes (the fec2152 file-move regression class)

The CoDA-MCP commit (fec2152) relocated every setup script into setup/ (and the install scripts into scripts/), but several scripts kept resolving sibling resources via Path(__file__).parent — which then pointed at setup/ instead of the repo root. Three resources were silently broken; all fixed and pinned with regression tests.

  • OpenCode (444e133): setup_proxy.py launched a nonexistent setup/content_filter_proxy.py, so the content-filter proxy never started and OpenCode — the only agent routed through 127.0.0.1:4000 — failed with Cannot connect to API. Other agents talk to the gateway directly and were unaffected. Now resolves from the repo root via resolve_proxy_script_path() (script body guarded under main() so the path logic is importable/testable). Also registered databricks-claude-opus-4-7 in the OpenCode model map and dropped a duplicate gemini-2-5-pro key.
  • Claude subagents (7bdc3f3): setup_claude.py looked in setup/agents/, silently hitting the "No agents directory found" branch — the TDD subagents (build-feature, prd-writer, test-generator, implementer) were never installed into ~/.claude/agents. Fixed via resolve_agents_src() → repo root.
  • Codex model catalog (7bdc3f3): setup_codex.py looked in setup/.codex/databricks-models.json; the catalog was never copied into ~/.codex while config.toml still referenced it. Fixed via resolve_codex_catalog_src() → repo root.

Regression tests pin each resolved resource path to a real file so a future move can't silently regress it again: tests/test_setup_proxy.py, tests/test_setup_resource_paths.py.

Follow-up #5: flaky PTY test-suite hardening (32624c7)

The full unit suite failed intermittently (a different set each run); two independent causes:

  • terminate_session double-closed master_fd (production bug): both the explicit close path (mcp_close_pty_session) and the read-thread exit path (read_pty_output) call it for the same session, but the kill/os.close block ran unconditionally — so the second os.close() could land on a since-reused fd (e.g. an asyncio event loop's self-pipe from a later test), surfacing as intermittent OSError: [Errno 9] Bad file descriptor. Now atomically claims the session (sessions.pop) and closes exactly once. Covered by tests/test_terminate_session_idempotent.py.
  • App-hook leak across test files: tests/test_mcp_server.py::_reset_hooks set mcp_server's process-wide PTY hooks to None in teardown, leaking into later files so coda_run created no PTY (pty_id is None) — but only in full-suite runs. Fixed with a tests/conftest.py autouse fixture that re-establishes app's real hooks after each test, making hook state order-independent.
  • TestNpmVersionLive now skips when the npm registry is unreachable (the skipif probe was raising TimeoutExpired as a collection error; the body asserted on a None result) instead of erroring/failing offline.

Full unit suite now green across 3 consecutive runs: 613 passed, 2 skipped.

Follow-up #6: skill-bundle refresh + Flutter / Dart / shadcn (f2ca8c7, 37df2f8)

  • Refreshed Databricks skills from databricks-solutions/ai-dev-kit (local was ~3 months stale: 2026-02-22 → upstream 76c774f, 2026-05-28). 24 → 26 skills: new databricks-ai-functions, databricks-execution-compute, databricks-iceberg; renamed app-python→apps-python, asset-bundles→bundles, synthetic-data-generation→synthetic-data-gen; dropped databricks-app-apx (no longer shipped upstream); content refreshed across the rest. The curated 16-skill list (coda_mcp/databricks_preamble.py) and the CLAUDE.md Databricks Skills table are kept in lockstep by test_databricks_preamble.py.
  • Bundled Flutter + Dart agent skills (per docs.flutter.dev/ai/agent-skills) so CoDA's agents can build Flutter/Dart apps: 10 flutter-* (flutter/skills), 9 dart-* (dart-lang/skills), and shadcn-ui-flutter (nank1ro/flutter-shadcn-ui). Documented in CLAUDE.md (skill count 30 → 50).

All of the above is verified live on the deployed test app mcp-test-coda-labs-feat (refreshed Databricks skills + the 20 new Flutter/Dart/shadcn skills confirmed in the deployed .claude/skills/).

datasciencemonkey and others added 30 commits April 9, 2026 10:04
…117)

Recompiled requirements.txt from pyproject.toml via uv pip compile.
Both packages pass the 7-day exclude-newer supply-chain gate.

- charset-normalizer 3.4.6 → 3.4.7 (released 2026-04-02)
- claude-agent-sdk 0.1.50 → 0.1.54 (released 2026-04-02)

Dependabot PRs #110 (pydantic-core), #111 (cryptography), #113
(claude-agent-sdk 0.1.58) propose versions blocked by the 7-day rule.
PR #109 (importlib-metadata 9.0.0) also blocked by UV exclude-newer.
….17.0)

Closes #118, #120, #121, #122

- Session creation prompt: ask users to reuse existing sessions before creating new
- MAX_CONCURRENT_SESSIONS backend cap (env var, default 5) with TOCTOU-safe check
- Session count label in tab bar with updates on all create/close/exit paths
- xterm.js ClipboardAddon for OSC 52 (copy-paste inside Claude Code)
- Write batching with requestAnimationFrame to prevent escape sequence fragmentation
- Alternate screen exit detection (auto-clear after Claude Code no-flicker/vim)
- SIGWINCH-based reattach (force redraw by toggling terminal size)
- 429 error message with hint to increase MAX_CONCURRENT_SESSIONS
- Replaced mlflow-tracing with mlflow-skinny 3.10.1
- PTY read chunk 4096→65536 bytes
- Fixed repo name in deployment docs
- Version bump to 0.17.0
Cherry-pick of #114 by David O'Keeffe (@dgokeeffe), rebased onto v0.17.0.

1. Normalize all emails to lowercase at ingestion points so SSO header
   casing differences don't cause authorization failures.
2. Probe auto-discovered AI Gateway URLs for reachability (2s timeout).
   Workspaces without AI Gateway gracefully fall back to serving-endpoints.
   Result cached in _GATEWAY_RESOLVED env var so subprocesses skip re-probing.
3. Version bump to 0.17.1.

Co-authored-by: David O'Keeffe <david.okeeffe@databricks.com>
…0.0.26

Databricks pypi proxy currently tops out at 0.0.24; fix version 0.0.26
isn't installable for clients, so we waive the audit alongside the
existing cryptography 46.0.7 waiver until the proxy catches up.

Co-authored-by: Isaac
Bumps [mlflow-skinny](https://github.com/mlflow/mlflow) from 3.10.1 to 3.11.1.
- [Release notes](https://github.com/mlflow/mlflow/releases)
- [Changelog](https://github.com/mlflow/mlflow/blob/master/CHANGELOG.md)
- [Commits](mlflow/mlflow@v3.10.1...v3.11.1)

---
updated-dependencies:
- dependency-name: mlflow-skinny
  dependency-version: 3.11.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
…skinny-3.11.1

chore(deps): bump mlflow-skinny from 3.10.1 to 3.11.1
Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.1.7 to 3.1.8.
- [Release notes](https://github.com/pallets/werkzeug/releases)
- [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst)
- [Commits](pallets/werkzeug@3.1.7...3.1.8)

---
updated-dependencies:
- dependency-name: werkzeug
  dependency-version: 3.1.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
…g-3.1.8

chore(deps): bump werkzeug from 3.1.7 to 3.1.8
Prevents dependabot from opening PRs for versions younger than 7 days,
aligning with the supply-chain safety gate in pyproject.toml and avoiding
CI failures where freshly released versions can't resolve under uv's
exclude-newer filter.

Co-authored-by: Isaac
Bumps [uvicorn](https://github.com/Kludex/uvicorn) from 0.42.0 to 0.44.0.
- [Release notes](https://github.com/Kludex/uvicorn/releases)
- [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md)
- [Commits](Kludex/uvicorn@0.42.0...0.44.0)

---
updated-dependencies:
- dependency-name: uvicorn
  dependency-version: 0.44.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
… (#133)

* fix: enforce owner auth on /api/sessions and /api/session/attach (#132)

These endpoints were incorrectly exempted from the before_request
authorization check, allowing any Databricks user to list sessions
and read buffered terminal output. Also adds 17 new tests covering
endpoint-level auth enforcement and case-insensitive email matching.

Fixes #132

* chore: bump version to 0.17.2
…-0.44.0

chore(deps): bump uvicorn from 0.42.0 to 0.44.0
…137)

- cryptography 46.0.6 → 46.0.7 (fixes GHSA-p423-j2cm-9vmq)
- python-multipart 0.0.22 → 0.0.26 (fixes GHSA-mj87-hwqh-73pj)
- claude-agent-sdk 0.1.54 → 0.1.58
- softprops/action-gh-release v2 → v3 SHA pin
- Remove --ignore-vuln flags from CI audit (both CVEs now patched)
- Regenerate requirements.lock with hashes
…d-steps)

Adds four Claude Code skills for behaviour-driven development on Databricks:

- bdd-scaffold: scaffold a Behave project wired to Databricks SDK (generates
  behave.ini, features/, steps/, environment.py, pyproject.toml with UV)
- bdd-features: write Gherkin feature files following Databricks-specific
  patterns (Unity Catalog operations, pipeline runs, SQL assertions)
- bdd-steps: generate Python step definitions from feature files using the
  Databricks SDK step library
- bdd-run: execute Behave suites with tag filtering, parallel mode, and
  CI-friendly reporting

Includes reference test suite for UC catalog and SQL operations.
Pure addition — no production code changes.

Co-authored-by: Isaac
* feat: integrate Hermes Agent as 5th coding CLI

Adds Hermes Agent (github.com/NousResearch/hermes-agent) alongside Claude
Code, Codex, OpenCode, and Gemini CLI. Hermes is a Python-based multi-
provider AI CLI with tool-calling, persistent memory, and a rich skill
system — installed via its official installer into ~/.local/bin/hermes.

Integration points:
- setup_hermes.py: installs Hermes, writes ~/.hermes/config.yaml pointing
  at Databricks AI Gateway (/mlflow/v1) or /serving-endpoints fallback.
  Configures custom provider, fallback_providers chain (opus-4-7 ->
  opus-4-6 on 429/529/503), external skills dir shared with Claude Code,
  and MCP servers (deepwiki + exa + optional team-memory).
- app.py: adds hermes to setup_state steps, parallel_steps, and the
  _configure_all_cli_auth re-run loop.
- cli_auth.py: _update_hermes() rewrites api_key lines in
  ~/.hermes/config.yaml on PAT rotation (every 10m).
- app.yaml.template: HERMES_MODEL env var (default opus-4-7).
- CLAUDE.md / README.md / docs/deployment.md: documentation.

Usage after deploy:
  hermes chat            # interactive chat
  hermes --tui chat      # rich TUI
  hermes model           # select default model
  hermes mcp list        # list configured MCP servers

* fix: replace 135MB git clone with uv tool install for hermes setup

The original setup_hermes.py cloned the full NousResearch/hermes-agent
repo (135MB) with a 180s timeout, which silently failed on Databricks
Apps. Switched to `uv tool install` from git URL — handles venv and
binary setup automatically. Also dropped the `matrix` extra (requires
native libolm).

* fix: post-setup token sync to prevent stale PAT in CLI configs

When PAT rotation happens while a setup script is still installing
(e.g., Hermes takes minutes to install from git), the rotation's
update_cli_tokens() silently skips missing config files. The setup
script then writes config with the initial (now-revoked) token.

Fix: after all parallel setup completes, re-apply the current token
to all CLI configs. This ensures every config has the latest token
regardless of installation timing vs rotation timing.

Closes the race window that caused HTTP 403 on first Hermes launch.

* fix: Gemini CLI auth — remove quoted .env value + strip stale env var

Two bugs preventing Gemini CLI from authenticating:

1. setup_gemini.py wrote GEMINI_API_KEY_AUTH_MECHANISM="bearer" (with
   literal quotes). Node.js dotenv parses this as '"bearer"' — Gemini
   CLI expects 'bearer' without quotes, fails auth check, falls back
   to interactive prompt asking for credentials.

2. Terminal sessions inherited GEMINI_API_KEY from the parent process
   env, which goes stale after PAT rotation. Now stripped from shell
   env (like DATABRICKS_TOKEN) so Gemini CLI reads from ~/.gemini/.env
   which is kept current by cli_auth.py.

* chore: update default Gemini model to databricks-gemini-2-5-pro

Updated in setup_gemini.py, setup_opencode.py, setup_hermes.py,
app.yaml, app.yaml.template, README.md, and deployment docs.

* fix: pre-trust workspace so Gemini CLI loads .env credentials

Gemini CLI has a workspace trust system that silently skips loading
.env files in untrusted directories (gemini-cli#20005). Terminal
sessions on Databricks Apps start in ~/projects/ which was never
trusted, so GEMINI_API_KEY from ~/.gemini/.env was never loaded.

Fix: write ~/.gemini/trustedFolders.json during setup to pre-trust
both ~/projects/ and ~/ so .env loading works from any directory.

* fix: use TRUST_FOLDER enum value in trustedFolders.json

Gemini CLI expects string enum values (TRUST_FOLDER, TRUST_PARENT,
DO_NOT_TRUST), not booleans. Using true caused:
  Invalid trust level "true" for path "..."

* fix: include mcp package in Hermes install for MCP server support

Hermes is installed via uv tool install into an isolated venv. The
mcp Python package (HTTP transport) wasn't included, so MCP servers
(DeepWiki, Exa) failed with "mcp.client.streamable_http not available."

Fix: add --with 'mcp>=1.2.0' to the uv tool install command.

---------

Co-authored-by: Hermes Agent <hermes@databricks.app>
feat: add BDD testing skills (bdd-scaffold, bdd-features, bdd-steps, bdd-run)
Adds SDK User-Agent based event tracking so CoDA usage appears on the
shared Labs usage dashboard. Events fire in background daemon threads
and never block Flask request handling or terminal I/O.

Active events tracked:
- app_startup (initialize_app)
- agent session creation with agent type (create_session)
- file_upload (upload_file)
- pat_rotation (_rotate_once)
- workspace_sync (sync_project)

Passive: all existing WorkspaceClient instances now carry product_info
('coda', version) so regular SDK calls also identify CoDA.

Reference: https://github.com/databrickslabs/dqx/blob/main/src/databricks/labs/dqx/telemetry.py
The original PR only updated setup_*.py, but the deployed app.yaml CODEX_MODEL
env var still pinned databricks-gpt-5-3-codex (overriding the new default), and
app.py had a parallel settings block still on opus-4-6. Also fixed the README
and deployment docs.

Co-authored-by: Isaac
…toml

Adds a model_catalog_json catalog so users can pick from gpt-5-5/5-4/5-4-mini/
5-3-codex/5-2 in the codex /model picker (instead of just the active default).

- Bundle .codex/databricks-models.json in the repo
- setup_codex.py copies it into ~/.codex/ at startup and adds
  `model_catalog_json = "databricks-models.json"` (relative path; codex's
  AbsolutePathBuf resolves relatives against CODEX_HOME)
- .gitignore now tracks only the bundled catalog under .codex/, so per-user
  generated config.toml stays untracked

Co-authored-by: Isaac
Codex looks for skills in \$HOME/.agents/skills and .agents/skills walking up
from cwd. We mirror the gemini setup pattern: copy .claude/skills (the
canonical bundled-skills source) into ~/.agents/skills at startup so codex
sees the same 30+ Databricks/superpowers skills the other agents do.

Also gitignore .agents/ since it's generated, like .gemini/skills.

Co-authored-by: Isaac
Notable updates:
- claude-agent-sdk 0.1.58 → 0.1.65
- databricks-sdk 0.102.0 → 0.103.0
- mcp 1.26.0 → 1.27.0
- certifi 2026.2.25 → 2026.4.22
- opentelemetry-sdk 1.40.0 → 1.41.0
- pydantic 2.12.5 → 2.13.3
- uvicorn 0.44.0 → 0.45.0

protobuf stays at 6.x (7.x is a major version bump, skipped)
mpkrass7 added 3 commits June 3, 2026 16:48
fix(deps): install requests from PyPI instead of a git+https GitHub pin
Remove promo video file and README fallback link
@datasciencemonkey datasciencemonkey self-assigned this Jun 4, 2026
mpkrass7 added 6 commits June 4, 2026 15:12
…6.5.20

chore(deps): bump certifi from 2026.4.22 to 2026.5.20
…astral-sh/setup-uv-8.1.0

chore(deps): bump astral-sh/setup-uv from 7.6.0 to 8.1.0
…ry-proto-1.42.1

chore(deps): bump opentelemetry-proto from 1.41.1 to 1.42.1
…36.3

chore(deps): bump fastapi from 0.136.1 to 0.136.3
chore(deps): bump zipp from 3.23.1 to 4.1.0
…13.4

chore(deps): bump pydantic from 2.13.3 to 2.13.4
@mpkrass7

mpkrass7 commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator
image Regression defect on gemini-cli

@mpkrass7

mpkrass7 commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator
image Opencode works as long as you don't change the model which defeats the purpose of opencode

@mpkrass7

mpkrass7 commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Likely not the PR but same problem in Hermes
image

@mpkrass7

mpkrass7 commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Again not the PR but we seriously need to figure out the cludgy terminal UI

image

@mpkrass7

mpkrass7 commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

MCPs from Genie Code are working

image

@mpkrass7

mpkrass7 commented Jun 7, 2026

Copy link
Copy Markdown
Collaborator
image

mpkrass7 and others added 6 commits June 7, 2026 10:25
…-comment

chore: drop the giant gitignore comment from #25 (post-merge nit)
Hot fix for dependabot update conflict
…ack)

PR #67 review flagged these for removal: docs/superpowers/{plans,specs}/*
and docs/plans/2026-05-01-coda-mcp-server.md are working artifacts of the
spec->plan->implement workflow, not user-facing docs.
…t 400, Hermes model catalog

- Gemini CLI broke on tool-call turns: newer gemini-cli attaches `id` to
  functionCall/functionResponse history parts; the Databricks /gemini route
  rejects unknown proto fields with 400 'Unknown name "id"'. Gemini CLI now
  routes through the local content-filter proxy, which strips those ids,
  forwards /gemini/* to the matching gateway route, detects Gemini streaming
  from the URL (:streamGenerateContent), and re-injects a fresh PAT after
  rotation (bonus: gemini sessions now survive PAT rotation).
- OpenCode 400'd after a model switch: it sends reasoning_effort, which
  Databricks chat-completions rejects ('Extra inputs are not permitted').
  The proxy now strips reasoning_effort/reasoning/verbosity from
  OpenAI-format request bodies.
- Hermes known_models catalog listed databricks-gemini-2-5-pro twice and
  omitted the default model databricks-claude-opus-4-7.

Tests: 21 new (proxy sanitizers, /gemini routing, stream detection, setup
wiring pins, hermes catalog hygiene). Full suite: 634 passed, 5 skipped.
@datasciencemonkey

Copy link
Copy Markdown
Collaborator Author

Review feedback addressed (22e3c59..3124da7 — the branch also now carries latest main: dep bumps, README refresh, #77 requests-from-PyPI fix).

1. Gemini CLI regression → fixed (3124da7). Root cause: newer gemini-cli releases attach id to functionCall/functionResponse history parts, and the Databricks /gemini route rejects unknown proto fields — hence the 400 Unknown name "id" at contents[].parts[0].function_call on every tool-call turn (we install "latest stable ≥7 days" so the CLI drifted under us; main would hit this on a fresh deploy too). Gemini CLI now routes through the local content-filter proxy (the one OpenCode already uses): it strips those ids, forwards /gemini/* to the gateway's Gemini route, and detects Gemini streaming from the URL (:streamGenerateContent). Bonus: the proxy injects the freshest PAT, so gemini sessions survive token rotation now.

2. OpenCode model switch → reasoning_effort 400 → fixed (3124da7). OpenCode sends reasoning_effort after a model change; Databricks chat-completions rejects unknown params ("Extra inputs are not permitted"). The proxy now strips reasoning_effort/reasoning/verbosity from OpenAI-format request bodies.

3. Hermes → partially fixed (3124da7). The bug on our side: setup_hermes.py's known_models catalog listed databricks-gemini-2-5-pro twice and omitted the default model databricks-claude-opus-4-7 entirely — fixed. The rest is upstream: the AI Gateway doesn't expose a model-listing endpoint at {gw}/mlflow/v1/models, and "No authenticated providers found" is Hermes' scan for known-provider API keys (custom providers don't count). Not fixable in this repo.

4. Cludgy terminal UI — agreed; deferring to its own issue/PR. It's xterm.js rendering/reflow work, orthogonal to this PR.

5. MCPs from Genie Code working — 🎉

6. Spec/plan artifacts → removed (d18d6ac). All 13 files: docs/superpowers/{plans,specs}/* + docs/plans/2026-05-01-coda-mcp-server.md.

21 new regression tests (proxy sanitizers, /gemini routing, stream detection, setup wiring pins, hermes catalog hygiene). Full suite: 634 passed, 5 skipped. Gemini/OpenCode fixes need a redeploy of mcp-test-coda-labs-feat to verify live.

The comment claimed OPENAI_API_KEY is written to a shell profile; the code
(correctly) writes only ~/.codex/.env, which cli_auth._update_codex()
rewrites on every PAT rotation. Spell out the rotation contract and warn
against profile/app.yaml exports — a frozen env copy would shadow the
rotating .env and pin Codex to a revoked token.
…lay through content-filter proxy

Codex reads OPENAI_API_KEY once at process start (config.toml env_key →
~/.codex/.env). cli_auth rewrites that file every rotation, so NEW sessions
always get the current token — but a RUNNING session froze its token and
401'd once the 10-min rotation revoked it. Codex now points at the local
content-filter proxy, which injects the freshest PAT per request.

The /openai/* prefix is relayed TRANSPARENTLY so zero Responses-API
capability is lost: byte-identical bodies both directions (no sanitizers,
no JSON re-serialization), all HTTP methods (GET/POST/DELETE), Accept-
Encoding identity, and connect-timeout-only so long silent reasoning
stretches never 504.

Critic-review hardening (verdict SHIP-WITH-FIXES, both blockers fixed):
- upstream response always closed; client disconnect mid-stream stops the
  relay (_write_chunk_strict propagates BrokenPipeError) instead of
  silently draining the upstream
- proxy now speaks HTTP/1.1 — chunked framing is invalid on 1.0 and strict
  hyper-based clients (Codex) can misparse it; pinned by resp.raw.version
- 204/304 forwarded bodyless (no chunked framing)
- elif prefix routing in resolve_upstream_url
- also: corrected a stale comment claiming OPENAI_API_KEY is written to a
  shell profile, and added an ast.parse syntax gate over setup/*.py (a
  text-pin test suite cannot catch syntax breaks — one occurred mid-dev)

Tests: +12 incl. live-socket relay fixture (fake upstream + real proxy):
byte-identical pass-through with reasoning fields intact, fresh-token
injection over a stale client header, GET/DELETE forwarding, verbatim SSE
(2^53+1 big-int canary), and a sanitized-path regression guard.
Full suite: 646 passed, 5 skipped.
@datasciencemonkey

Copy link
Copy Markdown
Collaborator Author

Follow-up (95005e8, fd971de): Codex now survives PAT rotation mid-session. Codex reads OPENAI_API_KEY once at startup, so a running session 401'd after the 10-min rotation revoked its token. It now routes through the content-filter proxy via a transparent /openai/* prefix — byte-identical relay (no sanitizers, no JSON re-serialization, all HTTP methods, no read timeout) with only the Authorization header replaced by the freshest PAT per request. Zero Responses-API capability lost by construction; pinned by live-socket tests (byte-identical bodies incl. reasoning fields, fresh-token injection, verbatim SSE). Proxy also bumped to HTTP/1.1 (chunked framing legality for strict clients). Independent code review: SHIP-WITH-FIXES, both blockers (upstream connection leak on client disconnect; prefix-routing fall-through) fixed before push. Suite: 646 passed.

Proxy topology now: OpenCode + Gemini + Codex via 127.0.0.1:4000; Claude + Hermes direct.

@dgokeeffe

Copy link
Copy Markdown
Collaborator

Review — sole maintainer doing a backlog pass. Short version: the MCP feature is worth landing, but not in this shape. Three things need resolving first, one of them a security regression. Detail below so this is actionable rather than just blocked.

Note #64 and #66 are strict ancestors of this branch, so I've closed them in favour of this one — git merge-base --is-ancestor confirms it contains their commits.


1. /mcp is auth-exempt, and that undoes #44

app.py adds /mcp to the before_request exempt list:

if request.path in (...) or request.path.startswith("/socket.io") or request.path.startswith("/mcp"):

and coda_mcp/mcp_endpoint.py declines to do its own checking:

53:    Permissive CORS for /mcp — the Databricks Apps proxy handles auth.
90:    # Origin validation skipped — Databricks Apps proxy handles auth.

The Apps proxy does force SSO to reach the app, so this isn't open to the internet. But CoDA's model is single-owner, fail-closed: check_authorization() compares X-Forwarded-Email against app_owner and denies everyone else. /mcp opts out of that, so any workspace user who can reach the app can drive it — and coda_run in mcp_server.py spawns PTY sessions, i.e. arbitrary code execution as the app identity, with the owner's PAT in ~/.databrickscfg.

Combined with permissive CORS and origin validation explicitly skipped on a browser-reachable endpoint, a cross-origin request from any site the owner visits would carry their SSO cookies.

This is the same class of hole #44 closed (which removed /api/setup-status, /api/pat-status and /api/app-state from that exact list). Landing this as-is reopens it on a more dangerous endpoint.

Suggested fix: owner-gate /mcp through the existing check_authorization(), and either restore origin validation or narrow CORS to a known allowlist. If MCP clients can't carry SSO, use a shared-secret header gate like /api/inject-pat does with CODA_BOOTSTRAP_SECRET — that's the established pattern here for non-SSO callers.

2. The entrypoint change is a much bigger deal than the diff suggests

-  - gunicorn
-  - app:app
+  - uvicorn
+  - coda_mcp.mcp_asgi:app

This moves production from WSGI to ASGI. Two consequences worth surfacing explicitly:

  • The comment concedes "WebSocket transport falls back to HTTP polling under uvicorn". That's a degradation of the core browser-terminal path, justified as acceptable because poll-worker.js exists. For a product whose whole value is a responsive terminal, that deserves to be an explicit decision, not a side effect of adding MCP.
  • CoDA's threading model assumes a single gunicorn worker with process-local PTY state (gunicorn.conf.py, and docs/agent-instructions.md §1). Whether that invariant still holds under uvicorn is unverified.

This cannot be validated without deploying. There is currently no working CI on this repo (see #120 — repository-defined workflows never schedule a job), so an entrypoint swap is exactly the change that must be proven on a live app first.

3. ~200 of the 234 files are unrelated churn

The feature itself is small and reviewable:

Part Size
coda_mcp/ 7 files, ~1,875 lines
tests 9 MCP-related files
docs 3 files

The rest is a repo-wide relocation (setup_*.pysetup/, install scripts → scripts/) plus a 176-file .claude/ skills refresh. That churn is what makes this unreviewable, and it maximises conflict surface against everything else in flight — it's also why the diff reads as +34k/-8k.

Suggested split:

  1. coda_mcp/ + its tests + docs + the minimal app.py wiring, with the auth fix from §1.
  2. The setup/ + scripts/ relocation as its own mechanical PR, if it's wanted at all.
  3. The .claude/ refresh via the existing refresh-databricks-skills skill, separately.

Happy to do the split myself if you'd rather — say the word. But §1 needs an answer from you either way, since it's a deliberate design choice in the current code rather than an oversight, and I'd rather not guess at the intent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants