feat(api-tox): v2.2.1 Phase 1.2.1 — UX polish (drag-in-and-it-works) - #38
Conversation
Fixes three friction points the live-debug session exposed:
1. `TDPILOT_API_INSECURE` was a process env var — gone on TD
restart. Users got 401 after every restart with no clear
remedy.
2. Pasting a new `Apikey` required two follow-up pulses
(`Saveapikey` + `Reloadconfig`) — non-obvious; users said
"I set the key but the agent doesn't see it".
3. After every `.tox` rebuild the chat panel's token rotated —
every open browser tab 401'd until the user knew to navigate
to `http://127.0.0.1:9987/` for a fresh one.
What's now in
=============
1. **`Authmode` COMP param replaces the env var as source of truth.**
New Menu param under the API page: `open` (default) / `token`.
`tdpilot_api_web_callbacks._insecure_mode` reads it first; env-var
becomes a fallback for back-compat. Per-request read, so flipping
the param takes effect immediately. Persists in the .toe.
Default is `open`: tokenless POST /send works. The origin
allowlist (v1.7.1) still enforces single-machine isolation, so
even Open mode rejects cross-origin browser CSRF. Suits TD's
single-user dev / live performance usage profile. Multi-machine
users flip `Authmode = token` once and the v2.1.3 token model
kicks back in.
2. **Auto-save + auto-reload on Apikey change.** `tdpilot_api_parexec`
now listens to `valuechange=1` (build script change). Filter is
narrow: only `Apikey` and `Authmode` route to the extension;
every other value change is a no-op. New
`Extension.OnApikeyValueChange` delegates to `OnSaveApiKeyPulse`
(which already saves to disk + reloads). Recursion-safe via
empty-string short-circuit. Drops the "type key, pulse Save,
pulse Reload" 3-step ritual to one paste.
3. **Stale-token reconnect banner in the chat panel.** New
`appendReconnectBanner()` JS helper in
`tdpilot_api_chat.html`. When `fetch('/send')` returns 401, the
panel renders a yellow message with a real `<button>` that calls
`window.location.reload()` — re-fetches `GET /` which already
bakes the current token into the served HTML. Bookmark-friendly
`http://127.0.0.1:9987/` always serves a working panel.
Tests (17 new in tests/test_v221_authmode_and_autoreload.py)
============================================================
- TestInsecureModeFromAuthmode (6): Authmode=open/token/whitespace/
unknown-falls-through; COMP param beats env var.
- TestBackwardsCompatibility (2): old .tox without Authmode param
still works via env var; `_comp()` returning None doesn't raise.
- TestAuthGateEndToEnd (3): Open mode lets tokenless /send through;
Token mode blocks; Open mode still rejects cross-origin.
- TestParexecValueChangeRouting (5): Apikey/Authmode route correctly;
other params no-op; missing-extension and raising-handler don't
crash the cook thread.
- TestApikeyEmptyShortCircuit (1): verifies the empty-string guard
prevents recursion through OnSaveApiKeyPulse's `Apikey.val=""` wipe.
Local sweep
===========
- pytest: 1838 passed (1821 prior + 17 new).
- ruff format + check: clean.
- check_versions: in sync at v2.1.5 (no bump on this PR; v2.2.0
cuts when Phase 1 is complete).
- check_tox_freshness (dpsk4): fresh — that .tox not touched.
- check_tox_api_freshness (chat-pipe): EXPECTED FAIL until user
rebuilds the .tox via the canonical Textport recipe.
Files modified for the .tox bake:
- build_tdpilot_api_tox.py (adds Authhdr + Authmode param;
enables valuechange=1 on parexec)
- tdpilot_api_web_callbacks.py (Authmode-based _insecure_mode)
- tdpilot_api_parexec.py (onValueChange routing)
- tdpilot_api_extension.py (OnApikeyValueChange + OnAuthmodeValueChange)
- tdpilot_api_chat.html (appendReconnectBanner + 401 catch branch)
Migration note: default auth posture changes from token-required to
origin-allowlist-only. Users sharing .toes across machines who
relied on the v2.1.3 token gate must explicitly set
Authmode = token on the COMP for that deployment.
MCP-server tox (port 9985) untouched — TD_MCP_SHARED_SECRET still
required for the MCP path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3ebc76845c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| value = str(comp.par.Authmode.val or "").strip().lower() | ||
| if value in ("open", "token"): | ||
| return value == "open" |
There was a problem hiding this comment.
Gate open auth mode behind the same exec clamp
Authmode=open now makes _insecure_mode() return True by default, which removes the X-TDPilot-Token requirement on /send, but the runtime hardening in TDPilotAPIExt._build_runtime still clamps TD_MCP_EXEC_MODE only when the env var TDPILOT_API_INSECURE is set. In practice this commit introduces a no-token path while leaving full exec enabled, so any local process (and LAN clients if the webserver is exposed) can drive agent turns that can reach td_exec_python with full privileges. The 2.1.3 mitigation needs to key off effective auth mode, not just the legacy env var.
Useful? React with 👍 / 👎.
First milestone of the v2.2.0→v3.0 roadmap (see docs/ROADMAP.md). Phase 1 ships in full; the chat-pipe agent is now safe to leave unsupervised on complex builds, two failure modes have automatic recovery, and drag-and-go finally works for new users out of the box. Bundled in this release ======================= - 1.1 Auto-rollback on error regression (PR #34 + #36) - 1.2 Cycle detection in tool chains (PR #37) - 1.2.1 Drag-and-go UX polish — Authmode COMP param, auto-save+auto-reload on Apikey change, 401 reconnect banner (PR #38) - 1.2.2 Build scripts auto-mirror .tox into main repo on rebuild, eliminating stale-symlink footgun (PR #39) - PR #35 paired source-file-list refactor (build-script + freshness- check now share a single source of truth) 136 new tests since v2.1.5. Total suite at release: 1848 passing. Headline behaviour change for end users ======================================= Default chat-pipe webserver auth posture changes from token-required to origin-allowlist-only (Authmode=open default). The origin allowlist still rejects cross-origin browser CSRF, so this is safe on TouchDesigner's typical single-user dev/perform usage profile. Users sharing .toe files across machines should set Authmode=token on the COMP for that deployment. The MCP-server tdpilot-dpsk4.tox (port 9985) auth model is UNCHANGED — TD_MCP_SHARED_SECRET still required. Bumps + rebuilt artifacts ========================= All 10 versioned files (7 manifests + 3 doc titles) bumped from 2.1.5 to 2.2.0 (scripts/check_versions.py enforces sync). API_VERSION in td_component/callbacks/_header.py also bumped; this invalidated both .tox source-hashes, both rebuilt inside TouchDesigner via the canonical recipe. Auto-mirror (Phase 1.2.2, just landed) propagated the rebuilt .tox files to the main repo's td_component/ automatically. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…38) * feat(api-tox): v2.2.1 Phase 1.2.1 — UX polish (drag-in-and-it-works) Fixes three friction points the live-debug session exposed: 1. `TDPILOT_API_INSECURE` was a process env var — gone on TD restart. Users got 401 after every restart with no clear remedy. 2. Pasting a new `Apikey` required two follow-up pulses (`Saveapikey` + `Reloadconfig`) — non-obvious; users said "I set the key but the agent doesn't see it". 3. After every `.tox` rebuild the chat panel's token rotated — every open browser tab 401'd until the user knew to navigate to `http://127.0.0.1:9987/` for a fresh one. What's now in ============= 1. **`Authmode` COMP param replaces the env var as source of truth.** New Menu param under the API page: `open` (default) / `token`. `tdpilot_api_web_callbacks._insecure_mode` reads it first; env-var becomes a fallback for back-compat. Per-request read, so flipping the param takes effect immediately. Persists in the .toe. Default is `open`: tokenless POST /send works. The origin allowlist (v1.7.1) still enforces single-machine isolation, so even Open mode rejects cross-origin browser CSRF. Suits TD's single-user dev / live performance usage profile. Multi-machine users flip `Authmode = token` once and the v2.1.3 token model kicks back in. 2. **Auto-save + auto-reload on Apikey change.** `tdpilot_api_parexec` now listens to `valuechange=1` (build script change). Filter is narrow: only `Apikey` and `Authmode` route to the extension; every other value change is a no-op. New `Extension.OnApikeyValueChange` delegates to `OnSaveApiKeyPulse` (which already saves to disk + reloads). Recursion-safe via empty-string short-circuit. Drops the "type key, pulse Save, pulse Reload" 3-step ritual to one paste. 3. **Stale-token reconnect banner in the chat panel.** New `appendReconnectBanner()` JS helper in `tdpilot_api_chat.html`. When `fetch('/send')` returns 401, the panel renders a yellow message with a real `<button>` that calls `window.location.reload()` — re-fetches `GET /` which already bakes the current token into the served HTML. Bookmark-friendly `http://127.0.0.1:9987/` always serves a working panel. Tests (17 new in tests/test_v221_authmode_and_autoreload.py) ============================================================ - TestInsecureModeFromAuthmode (6): Authmode=open/token/whitespace/ unknown-falls-through; COMP param beats env var. - TestBackwardsCompatibility (2): old .tox without Authmode param still works via env var; `_comp()` returning None doesn't raise. - TestAuthGateEndToEnd (3): Open mode lets tokenless /send through; Token mode blocks; Open mode still rejects cross-origin. - TestParexecValueChangeRouting (5): Apikey/Authmode route correctly; other params no-op; missing-extension and raising-handler don't crash the cook thread. - TestApikeyEmptyShortCircuit (1): verifies the empty-string guard prevents recursion through OnSaveApiKeyPulse's `Apikey.val=""` wipe. Local sweep =========== - pytest: 1838 passed (1821 prior + 17 new). - ruff format + check: clean. - check_versions: in sync at v2.1.5 (no bump on this PR; v2.2.0 cuts when Phase 1 is complete). - check_tox_freshness (dpsk4): fresh — that .tox not touched. - check_tox_api_freshness (chat-pipe): EXPECTED FAIL until user rebuilds the .tox via the canonical Textport recipe. Files modified for the .tox bake: - build_tdpilot_api_tox.py (adds Authhdr + Authmode param; enables valuechange=1 on parexec) - tdpilot_api_web_callbacks.py (Authmode-based _insecure_mode) - tdpilot_api_parexec.py (onValueChange routing) - tdpilot_api_extension.py (OnApikeyValueChange + OnAuthmodeValueChange) - tdpilot_api_chat.html (appendReconnectBanner + 401 catch branch) Migration note: default auth posture changes from token-required to origin-allowlist-only. Users sharing .toes across machines who relied on the v2.1.3 token gate must explicitly set Authmode = token on the COMP for that deployment. MCP-server tox (port 9985) untouched — TD_MCP_SHARED_SECRET still required for the MCP path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: rebuild tdpilot_API.tox for Phase 1.2.1 UX polish --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First milestone of the v2.2.0→v3.0 roadmap (see docs/ROADMAP.md). Phase 1 ships in full; the chat-pipe agent is now safe to leave unsupervised on complex builds, two failure modes have automatic recovery, and drag-and-go finally works for new users out of the box. Bundled in this release ======================= - 1.1 Auto-rollback on error regression (PR #34 + #36) - 1.2 Cycle detection in tool chains (PR #37) - 1.2.1 Drag-and-go UX polish — Authmode COMP param, auto-save+auto-reload on Apikey change, 401 reconnect banner (PR #38) - 1.2.2 Build scripts auto-mirror .tox into main repo on rebuild, eliminating stale-symlink footgun (PR #39) - PR #35 paired source-file-list refactor (build-script + freshness- check now share a single source of truth) 136 new tests since v2.1.5. Total suite at release: 1848 passing. Headline behaviour change for end users ======================================= Default chat-pipe webserver auth posture changes from token-required to origin-allowlist-only (Authmode=open default). The origin allowlist still rejects cross-origin browser CSRF, so this is safe on TouchDesigner's typical single-user dev/perform usage profile. Users sharing .toe files across machines should set Authmode=token on the COMP for that deployment. The MCP-server tdpilot-dpsk4.tox (port 9985) auth model is UNCHANGED — TD_MCP_SHARED_SECRET still required. Bumps + rebuilt artifacts ========================= All 10 versioned files (7 manifests + 3 doc titles) bumped from 2.1.5 to 2.2.0 (scripts/check_versions.py enforces sync). API_VERSION in td_component/callbacks/_header.py also bumped; this invalidated both .tox source-hashes, both rebuilt inside TouchDesigner via the canonical recipe. Auto-mirror (Phase 1.2.2, just landed) propagated the rebuilt .tox files to the main repo's td_component/ automatically. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Drag-in UX polish that emerged from the live-debug session against the v2.2.0 reliability features. Three friction points were silently making the chat-pipe a worse-than-it-needs-to-be experience for new users AND devs. This PR fixes all three.
Verified live in TouchDesigner pre-merge: maintainer rebuilt the .tox + sent
yofrom a fresh tab; chat round-trip succeeded with zero env-var toggles or pulse rituals.The three friction points
TDPILOT_API_INSECURE=1was a process env var — wiped on every TD restart, so users dropped back into 401-land after every reboot.Authmodeis a COMP param (Menu:open/token, defaultopen). Persists in the.toe. Read per-request, no Reloadconfig needed to flip.Apikeyvalue required two follow-up pulses (Saveapikey,Reloadconfig). Users said "I set the key but the agent doesn't see it."parexeclistens tovaluechange=1;Extension.OnApikeyValueChangedelegates to the existing save+reload path. Zero pulses required..toxrebuild the chat panel's CSRF token rotated. Every open browser tab 401'd until the user knew to manually navigate tohttp://127.0.0.1:9987/for a fresh one.<button>that callswindow.location.reload(). One-click recovery.Files
Modified (5 .tox-baked + 1 doc):
td_component/build_tdpilot_api_tox.py— addsAuthhdrsection +AuthmodeMenu param under the API page;_wire_parexecenablesvaluechange=1.td_component/tdpilot_api_web_callbacks.py—_insecure_modereadsAuthmodeCOMP param first; env var becomes a fallback for back-compat.td_component/tdpilot_api_parexec.py—onValueChangeroutes onlyApikeyandAuthmodeto the extension (every other value change is a no-op).td_component/tdpilot_api_extension.py— newOnApikeyValueChange+OnAuthmodeValueChangemethods. The Apikey handler has an empty-string short-circuit to prevent recursion throughOnSaveApiKeyPulse'sApikey.val = ""wipe.td_component/tdpilot_api_chat.html— newappendReconnectBanner()JS helper;/sendcatch branches on the401substring to surface the reload-button instead of the generic error.CHANGELOG.md— Phase 1.2.1 section under Unreleased.New (1 test file):
tests/test_v221_authmode_and_autoreload.py— 17 tests across 5 classes.Binary update (2):
td_component/tdpilot_API.tox+.tox-api-source-hash.json(rebuilt by maintainer in TD; freshness gate green at hash<new>).Tests
TestInsecureModeFromAuthmodeTestBackwardsCompatibility_comp()returning None doesn't raise.TestAuthGateEndToEnd/sendthrough; Token mode blocks; Open mode still rejects cross-origin (origin allowlist preserved — no LAN-RCE regression).TestParexecValueChangeRoutingTestApikeyEmptyShortCircuitOnSaveApiKeyPulse's post-save wipe.Test plan
uv run pytest tests/ --ignore=tests/agent_evals -q— 1838 passed (1821 prior + 17 new).uv run --extra dev ruff format --check src tests scripts td_component— clean.uv run --extra dev ruff check src tests scripts td_component— clean.uv run python scripts/check_versions.py— in sync at v2.1.5 (no bump; v2.2.0 cuts when Phase 1 is complete).uv run python scripts/check_tox_freshness.py— dpsk4 .tox fresh (not touched by this PR).uv run python scripts/check_tox_api_freshness.py— API .tox fresh post-rebuild.bash scripts/check_no_personal_paths.sh— clean.yofrom a fresh tab. Round-trip succeeded with no auth toggle (proving default Authmode=open works as designed).Security model — explicit migration note
The default chat-pipe webserver auth posture changes from token-required to origin-allowlist-only. This is appropriate for TouchDesigner's typical usage pattern (single-user creative coding on a personal box) and addresses the real friction it was creating, but it is a defaults change worth flagging:
POST /senddirectly. The threat model assumes you trust other code running on your own machine — same assumption already implicit in TouchDesigner's parameter system.0.0.0.0(LAN-reachable), Open mode means anyone on the LAN can drive your chat-pipe. TheAuthmode = tokenflip restores the v2.1.3 protection one click away. Users in shared studio / venue / coffee-shop networks should flip it; users on personal laptops are fine on Open.This trade-off is documented in the COMP's
Authhdrsection + CHANGELOG. Thetdpilot-dpsk4.toxMCP server (port 9985) auth is untouched by this PR.Why this should ship now (before Phase 1.5 / 2.1)
This is the kind of "rough edge nobody catches until a real user tries the system end-to-end" friction. The live-debug session in this session's earlier context surfaced exactly the failure modes this PR addresses — they're real, they're cheap to fix, and waiting to ship them means every additional Phase 1.x rebuild compounds the same workflow papercut.
🤖 Generated with Claude Code