Skip to content

Releases: dreamrec/TDPilot_deepseekv4

v2.5.5 — release ritual + td_ingest_url ship

Choose a tag to compare

@dreamrec dreamrec released this 19 May 15:04
797960b

v2.5.5 — release ritual + td_ingest_url ship (post-PR-#56)

Follow-up release that tags the source work from PR #56, which landed the td_ingest_url MCP tool + argument-shape parity tests on main with [skip-version-check] — the documented escape hatch in v2.5.4's tag-freshness CI gate. This release runs the standard ritual that was deferred.

🆕 td_ingest_url — v2.6.3 first slice

New MCP tool at src/td_mcp/registry/tools_ingest.py backed by src/td_mcp/web/ingest.py. Lets the agent fetch a public HTTPS page and pipe it through markitdown (optional [web] extras) so the resulting Markdown can be cited as context inline. The full v2.6.3 plan in docs/plans/v2.6_IMPLEMENTATION_PLAN.md was 3 days of work; this is the core fetch + sandbox slice (~½ day) — enough to be usable, with a follow-up required to expose the same tool through the chat-pipe surface.

Security model (minimal viable SSRF defense — not DNS-rebinding resistant; that's deferred to a v2.6.3 hardening follow-up):

  • HTTPS only. file://, http://, javascript:, data:, ftp://, gopher:// rejected before any network call.
  • Loopback / RFC1918 / link-local literals rejected. Covers the cloud metadata service literal 169.254.169.254, IPv6 ULA fc00::/7, IPv6 link-local fe80::/10, the 172.16-31.x RFC1918 range with tight bounds-check.
  • No redirects. A custom _NoRedirectHandler aborts redirects so an attacker can't bait https://attacker.example.com/redirect?to=http://localhost past the host allowlist.
  • Per-request timeout via TDPILOT_INGEST_TIMEOUT env (default 30 s).
  • Max response size cap via TDPILOT_INGEST_MAX_BYTES (default 5 MB).
  • Identifying User-Agent so server logs can correlate.

Optional dep: pip install -e .[web] installs markitdown>=0.0.1. Without it, td_ingest_url returns a structured {"error": "web_extras_not_installed", "advice": ...} response — same pattern as td_ocr_image.

🧪 Argument-shape cross-runtime parity test

Closes the code-quality agent's "lower-frequency gap" finding from the 2026-05-19 fresh audit. The v2.5.4 schema↔handler name-parity tests catch drift in tool NAMES; v2.5.5 adds two new tests that catch drift in tool ARGUMENTS:

  • test_chat_pipe_required_args_exist_on_mcp_side — for each well-known shared tool (td_get_node_detail, td_create_node, td_set_params, td_get_content), the chat-pipe's input_schema.required keys must also appear in the MCP-side inputSchema.properties. Intentional translations (e.g. chat-pipe op_type → MCP node_type) are whitelisted via INTENTIONAL_ARG_TRANSLATIONS.
  • test_chat_pipe_schemas_have_consistent_structure — every chat-pipe schema entry must declare input_schema of type "object". Catches the bare {"name": "x"} regression class.

📊 Tool count: 109 → 110

EXPECTED_MIN_TOOL_COUNT in src/td_mcp/release_gates.py bumped to 110. mcp/manifest.json:surface.tool_count matches. tests/fixtures/tool_schemas.json snapshot regenerated to include td_ingest_url.

📝 Project CLAUDE.md — 7-file checklist → real 13-file checklist

The 2026-05-19 audit found that the project CLAUDE.md's "all 7 must match" version-bump checklist was stale — scripts/check_versions.py actually enforces 13 files (docs and skills carry user-visible version strings too). Updated with the real list + a note about the v1.8.3 / PR-#16 mcp_webserver_callbacks.pycallbacks/_header.py migration.

Both .tox files rebuilt against v2.5.5 API_VERSION

The source-only sweep (PR-#56) shipped to main with zero td_component/ changes and both .tox gates green. This release bumps td_component/callbacks/_header.py::API_VERSION from "2.5.4" to "2.5.5" — one byte change in a single file. That file is in the source list for both the MCP .tox (td_component/.tox-source-hash.json) and the API .tox (td_component/.tox-api-source-hash.json, where the callbacks/ package is bundled for the chat-pipe's MCP-server fallback), so the version bump trips both freshness gates. Both files rebuilt in TouchDesigner 2025.32820:

  • MCP .tox hash 3dfaf709ff39a0be...
  • API .tox hash 821f8a6604e031f9...

Test totals

Source-only sweep: 2170 → 2205 passing (+35, +0 regressions; 4 skipped: 1 paddleocr E2E + 3 mock-eval scenarios pending fixture capture).

Operational

  • All 13 version manifests synced at 2.5.5 (validated by scripts/check_versions.py)
  • PR #56 (source) merged as 1ef8855; PR #57 (ritual) merged as 797960b. Both PRs CI green.

Assets attached

  • tdpilot-dpsk4.tox — MCP-server-side .tox (drag into TD for the Claude Code CLI variant)
  • tdpilot_API.tox — chat-pipe standalone .tox (drag into TD for the standalone browser-tab chat)
  • tdpilot.plugin — Claude Code plugin ZIP (built + attached by release-assets.yml)
  • tdpilot.mcpb — Claude Desktop one-click bundle (built + attached by release-assets.yml)

Generated with Claude Code.

v2.5.4 — hardening release (audit follow-ups + tag-freshness CI gate)

Choose a tag to compare

@dreamrec dreamrec released this 19 May 13:49
ea1e8df

v2.5.4 — hardening release (audit follow-ups + tag-freshness CI gate)

Closes the last outstanding items from the 2026-05-19 fresh audit:

  • C-1 part B — MCP-side Origin allowlist in callbacks/router.py
  • M-1 — Traceback path redaction in callbacks/router.py
  • N-1 — First-run UX hint in autostart.py when default-secure mode has no secret installed
  • N-2 — 8 H-1 regression tests (previously zero coverage)
  • NEW — Cross-runtime schema parity test (snapshot-style)
  • Section C — Mock-eval scenarios scaffolded (3 skipped pending fixture capture)
  • Tag-freshness CI gate prevents the v2.5.3 dual-state failure from recurring

Both .tox rebuilt against v2.5.4 sources. All 13 version manifests synced.
2141 → 2170 tests passing. PR #55 squash-merged to main as ea1e8df.

Governance hardening landed via gh api:

  • Branch protection on main (6 required CI checks)
  • Dependabot security alerts + auto-fix
  • delete_branch_on_merge enabled

v2.5.3 — cycle-detect rollback-hint preservation (Codex P2 follow-up)

Choose a tag to compare

@dreamrec dreamrec released this 19 May 10:43
365bf4e

v2.5.3 — cycle-detect rollback-hint preservation (Codex P2 follow-up)

Direct follow-up to v2.5.2. ChatGPT-Codex's automated review on PR #51 caught a real follow-on bug:

When a guarded mutation earlier in the same tool batch introduces a critical TD error and a later tool_use trips cycle detection, this appends the user tool_result message before AutoRollbackGuard.__exit__ runs and before _apply_rollback_hint can attach the rollback notice. The finally can still roll the mutation back and populate hint_text, but the raise skips the normal hint path, so the next API call sees a successful result for a change that was reverted and no recovery hint.

The structural issue

v2.5.2's fix appended the synthetic tool_result user-role message to agent.messages BEFORE the outer try/finally ran rollback_guard.__exit__. So when the guard rolled back a mutation and populated guard.hint_text, _apply_rollback_hint (at tdpilot_api_agent.py:1219, AFTER the finally) never got to attach that hint to the already-persisted results_block. Model on next turn would see tool_result: success and try to interact with a path that no longer existed.

The fix

Inside the cycle-detect block, BEFORE messages.append:

  1. Run rollback_guard.__exit__(None, None, None) synchronously so hint_text gets populated.
  2. Call self._apply_rollback_hint(rollback_guard, results_block) to attach the hint to the last tool_result.
  3. Set rollback_guard = None so the outer try/finally skips a second __exit__ (avoid double-rollback).
  4. messages.append(...) — now with the hint attached.
  5. raise CycleDetected(...).

Tests

5th test added to tests/test_v252_cycle_detect_orphan_tool_use.py:

  • test_v253_cycle_detect_preserves_rollback_hint_on_terminal_result — uses a FakeGuard stub that populates hint_text on __exit__; asserts the terminal tool_result content carries the hint.

Full suite: 2112 → 2113 passing (+1, 0 regressions).

Operational

  • Both .tox files rebuilt with API_VERSION = "2.5.3":
    • MCP .tox hash def2e9553c6c0c91
    • API .tox hash cd559a8954b0fc45
  • 13 version files bumped 2.5.2 → 2.5.3 (validated by scripts/check_versions.py)
  • CI on PR #52: 6/6 green

Assets attached

  • tdpilot-dpsk4.tox — MCP-server-side .tox
  • tdpilot_API.tox — chat-pipe standalone .tox
  • tdpilot.plugin — Claude Code plugin ZIP (built + attached by release-assets.yml)
  • tdpilot.mcpb — Claude Desktop one-click bundle (built + attached by release-assets.yml)

Two consecutive patches today from external review: v2.5.2 closed Bug A from the 10-task live audit; v2.5.3 closes Codex's follow-up review of v2.5.2. Both bugs lived in the same Agent._loop dispatch surface — first the orphan tool_use, then the orphan _apply_rollback_hint call after the orphan-tool_use fix re-ordered the messages.append. Codex catching this within minutes of v2.5.2 landing is a strong endorsement of the automated-review pipeline.

Generated with Claude Code.

v2.5.2 — cycle-detect orphan tool_use fix (live-audit Bug A)

Choose a tag to compare

@dreamrec dreamrec released this 19 May 10:26
ae04bc7

v2.5.2 — cycle-detect orphan tool_use fix (live-audit Bug A)

Patch release closing a production-blocker found by the 2026-05-19 10-task creative live audit.

Before the fix

When CycleDetected raised inside Agent._loop's dispatch for-loop, no synthetic tool_result block was appended for the pending tool_use id. The persisted conversation at ~/.tdpilot-api/history/<session>.jsonl then had an orphan tool_use that Anthropic-format /v1/messages rejected with HTTP 400 on every subsequent /send:

messages.12: `tool_use` ids were found without `tool_result` blocks immediately after:
  call_00_mK1LfJgrh5xN53iJ8Nn97410.

Chat-pipe became stuck until TouchDesigner restart — clearing chat_transcript, moving the JSONL aside, and pulsing Reinit Extensions were all insufficient because the agent worker thread kept an in-memory messages cache.

What changed

At the cycle-detect raise site in td_component/tdpilot_api_agent.py:1048, before raise CycleDetected:

  1. Synthesize one tool_result block per pending tool_use — the offending one plus any remaining un-dispatched batch entries.
  2. Each synthetic block carries is_error=True + a _tool_error sentinel + a descriptive cycle_detected: ... error string so downstream recovery + activity-ring + journal-hint paths handle it without special-casing.
  3. Append the user-role message to agent.messages before raising. The persisted conversation stays API-valid and the next /send succeeds without intervention.

Net delta: ~25 lines in one method + 1 new test file.

Tests

New tests/test_v252_cycle_detect_orphan_tool_use.py — 4 tests pinning the invariant:

  • No orphan tool_use_id after CycleDetected raises
  • Synthetic result carries is_error=True + _tool_error sentinel
  • Batched tool_use blocks all get matching results
  • Terminal message is role=user (never assistant)

Full suite: 2108 → 2112 passing (+4, 0 regressions).

Behavioral observation (logged, not fixed in this patch)

DeepSeek-v4-pro can ignore the v2.5.1 _read_journal hint that fires at count=2 ("one more identical call will trip cycle-detect"). The hint mechanism works correctly; compliance is model-discretionary. The audit's pre-fix run captured this — the model emitted tool_batch({}) with empty args 3× in a row despite receiving the hint at count=2. Worth a harder-stop or stronger phrasing in a future runtime version.

Operational

  • Both .tox files rebuilt with API_VERSION = "2.5.2":
    • MCP .tox hash 3d3aaefacee0c9d5 (49.6 KB)
    • API .tox hash 3e0f842ab226253b (282.6 KB)
  • 13 version files bumped 2.5.1 → 2.5.2 (validated by scripts/check_versions.py)
  • CI on PR #51: 6/6 green (lint + 3 Python versions + 2 install-parse)

Assets attached

  • tdpilot-dpsk4.tox — MCP-server-side .tox (drag into TD for the Claude Code CLI variant)
  • tdpilot_API.tox — chat-pipe standalone .tox (drag into TD for the standalone browser-tab chat)
  • tdpilot.plugin — Claude Code plugin ZIP (built + attached by release-assets.yml)
  • tdpilot.mcpb — Claude Desktop one-click bundle (built + attached by release-assets.yml)

Generated with Claude Code.

v2.5.1 — chat-pipe alias for td_get_traces (live-audit follow-up)

Choose a tag to compare

@dreamrec dreamrec released this 18 May 23:44

v2.5.1 — chat-pipe alias for td_get_traces (live-audit follow-up)

Tiny but operationally meaningful patch on top of v2.5.0: closes a chat-pipe surface gap that the v2.5.0 live audit found.

What changed

The v2.5.0 chat-pipe agent's 94-tool standalone surface did not expose td_get_traces — the trace viewer added MCP-side in v2.5.8 — because the chat-pipe dispatcher already registered the handler under the long name handle_get_recent_traces. Calling td_get_traces from the chat returned "exact tool name isn't in my surface".

v2.5.1 adds td_get_traces as a TOOL_TO_HANDLER + TOOL_SCHEMAS alias. Net code delta: 4 lines in td_component/tdpilot_api_schema_map.py + 1 schema-defs entry. The MCP CLI surface is unchanged.

Tool surface

Surface v2.5.0 v2.5.1
MCP CLI (npx tdpilot-dpsk4) 109 109 (unchanged)
chat-pipe standalone (tdpilot_API.tox) 94 95

Live verification (T6)

Against the rebuilt API .tox:

user:        Call td_get_traces with limit=5 and tell me how many records...
tool_call:   td_get_traces({'limit': 5})
tool_result: td_get_traces → ok
assistant:   5 trace records came back.

Operational

  • Both .tox rebuilt with API_VERSION = "2.5.1" (preserves the v1.6.5 lockstep invariant)
  • MCP .tox hash 16500d6ed91cc9a0, 49.6KB
  • API .tox hash 8b3d088bce84918c, 282.6KB
  • 13 version files bumped 2.5.0 → 2.5.1 (validated by scripts/check_versions.py)

Test results

  • CI: 6/6 green on PR #48 (lint + 3 Python versions + 2 install-parse)
  • Local pytest: 2108 passed, 1 skipped (paddleocr e2e), 12 deselected

Assets attached

  • tdpilot-dpsk4.tox — MCP-server-side .tox (drag into TD for the Claude Code CLI variant)
  • tdpilot_API.tox — chat-pipe standalone .tox (drag into TD for the standalone browser-tab chat)
  • tdpilot.plugin — Claude Code plugin ZIP (built + attached by release-assets.yml)
  • tdpilot.mcpb — Claude Desktop one-click bundle (built + attached by release-assets.yml)

Deferred items (still open for a follow-up patch)

The v2.5.0 live audit identified two more chat-pipe surface gaps that v2.5.1 does not close, because they require larger refactors than an alias:

  • v2.5.1.2td_get_activity_log in chat-pipe (requires promoting ActivityRing to a module singleton, ~30 min)
  • v2.5.1.3td_check_for_updates in chat-pipe (requires duplicating ~300 LOC of src/td_mcp/lifecycle/update_check.py in the restricted-mode-compatible chat-pipe codebase)

Both are tracked in docs/plans/v2.5_IMPLEMENTATION_PLAN.md.


Generated with Claude Code.

v2.5.0 — Agent self-awareness + safety + distribution polish

Choose a tag to compare

@dreamrec dreamrec released this 18 May 23:01

Agent self-awareness + safety + distribution polish. Eight phases shipped end-to-end: activity log + journal hints (v2.5.1), OCR sidecar (v2.5.2), tool approval gates (v2.5.3), auth env→file migration (v2.5.4), TD 2025.32820 release card (v2.5.5 — already-shipped), stdio discipline contract test (v2.5.6), td_check_for_updates (v2.5.7), trace viewer (v2.5.8). Tool count 105 → 109. Live-debug-style live-TD verification on the rebuilt .tox: all 6 new modules import cleanly, is_approval_required + build_denied_result return exact contract outputs against the live COMP. Full suite: 2000 → 2099 passing (+99, +1 skipped paddleocr e2e). CI green across lint + test (3.10/3.11/3.12) + install-parse (macos/windows).

Four new MCP tools — td_get_activity_log, td_ocr_image (requires [ocr] extras), td_check_for_updates, td_get_traces — plus a new Approvalmode Menu COMP param that gates destructive tools (td_exec_python, td_delete_node, td_restore_snapshot, snapshot_restore_scoped, td_disconnect always; td_rename_node / td_set_content when path is outside the agent's own COMP) with a chat-banner click-through (30 s timeout). TDPILOT_DISABLE_TOOL_APPROVAL env var hard-overrides for CI / unattended use.

Per-phase detail below preserved verbatim from the development run.


Released — v2.5.0 phase-by-phase notes (originally drafted as Unreleased)

Phase v2.5.1 — Activity log + journal hints (2026-05-18)

Agent self-awareness foundation for the v2.5 release theme ("agent self-awareness + safety + distribution polish"). Closes the upstream dreamrec/TDPilot v1.6.16 gap (td_get_activity_log) and adds a runtime-level companion to the v2.4 B-007 cycle-detect protocol.

  • New MCP tool td_get_activity_log — exposes a 200-entry ring buffer of every tool dispatch this server session. Supports filtering by tool name and since_ts. Mirrors upstream's surface so agents migrating between forks see the same observability tool. Tool count 105 → 106.
  • Chat-pipe activity ringtd_component/tdpilot_api_activity_log.py. Per-turn instance; reuses the B-010 deep-canonical args_hash from tdpilot_api_cycle_detector so the two systems stay in lockstep.
  • _read_journal hints in tool results — when the chat-pipe agent calls the same tool with byte-identical args twice this turn, the next tool_result carries a _read_journal block with call_count, calls_until_cycle_detect, and a strategy-switch nudge. Fires at count=2 — one call short of cycle-detect ending the turn (threshold=3). Loop-prone probes (per B-007: td_get_errors, td_analyze_frame, td_get_node_detail, td_cooking_info, td_get_connections) get a stronger protocol-point-6 reinforcement message.
  • SYSTEM_PROMPT_BASE protocol point 6 expanded to acknowledge the runtime _read_journal field so the LLM links the static prompt guidance to the runtime signal.
  • Env var TDPILOT_DISABLE_ACTIVITY_LOG — escape hatch (parity with TDPILOT_DISABLE_CYCLE_DETECTION).
  • Cross-implementation parity tests pin MCP-side (src/td_mcp/observability/) and chat-pipe-side (td_component/tdpilot_api_activity_log.py) args_hash + hint shape — drift fails CI immediately.

Tests: +26 (13 ring + 13 hint, including a B-010 list-order-invariance regression). Full suite: 2000 → 2026 passing.

.tox rebuild required: API .tox only. _API_TOX_SOURCE_FILES changed. MCP .tox unaffected.

See docs/plans/v2.5_IMPLEMENTATION_PLAN.md §2 for the full phase design.

Phase v2.5.4 — Auth fallback: env→file migration (2026-05-18)

Closes the last drag-and-go cold-start hole. Pre-fix: user exports TD_MCP_SHARED_SECRET in their shell, restarts TD, gets 401 because the shell env didn't survive the relaunch. Post-fix: first server start with env-supplied secret persists it to ~/.tdpilot-dpsk4/.tdpilot-dpsk4.env; subsequent launches read from the file regardless of shell state.

  • New maybe_migrate_env_to_file(path) in auth_bootstrap.py — idempotent (same secret already in file = no-op), preserves non-secret lines, overwrites different values, writes via atomic temp file with 0o600 perms on POSIX, never leaks secret material to stdout.
  • Composed into bootstrap_auth after load_env_file and before maybe_generate_secret — so a shell-set secret persists before autogen could otherwise overwrite it.
  • +9 tests in tests/test_v25_auth_fallback.py covering all branches (no-env, new file, idempotent same value, overwrite different, preserve other lines, POSIX perms, integration via bootstrap_auth, no-stdout-leak).

Most of the file-fallback infrastructure was already present from the original v1.4.5 fix (load_env_file + maybe_generate_secret + canonical ~/.tdpilot-dpsk4/.tdpilot-dpsk4.env path) — v2.5.4 adds only the env→file direction.

Phase v2.5.5 — TD 2025.32820 release card (already shipped)

The TD 2025.32820 release card was already present at src/td_mcp/knowledge/cards/release/2025.32820.json (118 lines, 17 new ops + 21 changed ops + migration warnings + SDK versions). Indexed and exercised by tests/test_skill_content.py and tests/_mock_dispatcher.py. No new code required for this phase.

Phase v2.5.6 — Stdio discipline contract test (2026-05-18)

Stdout is the MCP transport channel under stdio mode — any rogue print() corrupts JSON-RPC framing and breaks Claude Desktop / Claude Code. Upstream dreamrec/TDPilot v1.6.12 fixed a regression of this kind. v2.5.6 pins the contract via tests/test_v25_stdio_discipline.py so regressions get caught in CI rather than at the user's TD startup.

  • +12 tests total:
    • 10 parameterized import tests covering hot-path modules (td_mcp, td_mcp.audit, td_mcp.auth_bootstrap, td_mcp.capabilities, td_mcp.errors, td_mcp.observability, td_mcp.observability.activity_log, td_mcp.release_gates, td_mcp.services, td_mcp.telemetry) — each MUST import with zero bytes on stdout
    • 1 hot-path test: record_activity (called on every _forward tool dispatch) must not print to stdout
    • 1 AST-based static scan of hot-path source files for print(...) calls that lack file=sys.stderr — robust against multi-line print formatting (the first heuristic flagged opening lines of multi-line stderr-targeted prints; AST walks the actual Call nodes)

Audit findings: no rogue stdout-prints in hot-path modules; CLI subcommands in td_mcp.server (doctor, mcp-config, autopin) intentionally print to stdout because they're invoked via tdpilot <subcommand> outside stdio mode — the AST test excludes those by scope.

v2.5 tests overall

+59 from baseline (2000 → 2047 passing):

  • v2.5.1 activity log + journal hints: +26
  • v2.5.4 env→file migration: +9
  • v2.5.6 stdio discipline contract: +12
  • (regenerated tool-schema snapshot included td_get_activity_log)

v2.5 status

4 / 8 v2.5 phases complete (or already-done). Remaining: v2.5.2 OCR, v2.5.3 tool approval, v2.5.7 check_for_updates, v2.5.8 log receiver (stretch). See docs/plans/v2.5_IMPLEMENTATION_PLAN.md.

Phase v2.5.2 — OCR sidecar (MCP-server side) (2026-05-19)

Subprocess-based OCR via PaddleOCR. The ~400 MB OCR model never bloats the MCP server's RAM footprint because the worker lives in a separate process and is only spawned on first request. v2.5.2 lands MCP-server-side OCR with the new td_ocr_image tool; chat-pipe-side integration (decoding the Phase B screenshot b64 and feeding it to the worker) is deferred to a v2.5.2.1 follow-up after chat-pipe-Python-vs-MCP-Python subprocess details settle.

  • New module src/td_mcp/vision/ocr.pyOcrManager owns the worker lifecycle: lazy spawn on first request, ~5 s warm-up, idle-kill after 5 min, restart-on-crash up to 3× with exponential backoff, per-request 30 s timeout. Tuning via TDPILOT_OCR_REQUEST_TIMEOUT / TDPILOT_OCR_IDLE_KILL / TDPILOT_OCR_MAX_RESTARTS env vars.
  • New worker src/td_mcp/vision/ocr_worker.py — standalone Python script that reads JSON lines on stdin, runs PaddleOCR, writes JSON lines on stdout. PaddleOCR import is lazy (first request only); subsequent calls reuse the in-memory model. Per-language cache so multilingual sessions don't reload the model on each lang= switch.
  • New MCP tool td_ocr_image(path, lang='en') — returns text + per-string bounding boxes + per-string confidence + elapsed_ms. Graceful failure: OcrUnavailable (extras missing) → user-facing advisory with install command; OcrTimeout → advisory with env-var tuning suggestion; FileNotFoundError → simple path check. Tool count 106 → 107.
  • New optional dep group [project.optional-dependencies] ocrpaddleocr>=2.7 + paddlepaddle>=2.6. Install via pip install -e .[ocr]. Base install size unchanged.
  • Pre-flight check — before spawning the worker the manager runs python -c 'import paddleocr' in a subprocess so missing extras surface as a clean OcrUnavailable advisory, not a vague "worker died".
  • +10 tests in tests/test_v25_ocr.py covering pre-flight failure, manager happy path (spawn, reuse, result shape), error paths (missing image, worker error response, worker import-error response), singleton lifecycle, and a @pytest.mark.skipif(not paddleocr_installed) end-to-end test against a synthesized PIL PNG.

Chat-pipe integration deferred: the chat-pipe agent runs inside TD's Python which doesn't share a venv with the MCP server. Subprocess spawning from TD's Python has PATH + native-lib complications. v2.5.2.1 will resolve via either (a) HTTP-call to the MCP server's OCR tool, or (b) bundled-venv detection.

Phase v2.5.7 — td_check_for_updates (2026-05-19)

Read-only update awareness. Compares the running MCP server version against the latest GitHub Release on dreamrec/TDPilot_deepseekv4 AND checks whether both .tox source hashes match the current ...

Read more

v2.4.0 — Phase A/B/C + B-001..B-010 live-debug stack

Choose a tag to compare

@dreamrec dreamrec released this 13 May 01:55

Multi-phase v2.4 release. Phase A (zero-risk MCP additions) + Phase B (vision pipeline + auth wizard + content_type) + most of Phase C (cost tracking, capability summary, MIDI, circuit breaker, thinking budget), plus a same-day live-debug stack closing 10 bugs (B-001..B-010) surfaced while running the canonical failing prompt "Build a kaleidoscope feedback loop" end-to-end. Tool count 93 → 105.

Live verification

The same prompt that failed every attempt before this release now succeeds with smooth-trail feedback:

  • Routed pro from turn 1 (B-008-A structural-noun signal)
  • Sticky-pro held across 30+ debug turns (B-008-T)
  • Two cycle-detect events surfaced cleanly (B-005), each forcing strategy switch
  • Agent self-diagnosed numerical bug: per-frame decay 0.85 × 0.9 = 0.765 was too aggressive; shipped 0.95/frame smooth trails
  • 174 chat rows, 118K in / 27K out / 59 cache hits / 0 misses / $0.062 total

v2.4 phases shipped

  • Phase Atool_batch, retry-with-backoff, td_get_capabilities_summary, td_cooking_info GPU/CUDA sort_by, on_hint UI surface.
  • Phase B — Vision pipeline (screenshot → image block injection), first-run auth wizard (Authmode=token default), content_type field on memory/knowledge/recipe entries.
  • Phase C (partial) — Per-session cost tracking with footer pill, td_midi_devices, circuit breaker (C.8), configurable thinking budget (C.9), featured-prompts chip row (C.6). DMX (C.1) deferred to v2.5.

Live-debug — 10 bugs closed

Bug Layer Description
B-001 Schema Chat-pipe td_cooking_info sort_by enum missing GPU/CUDA
B-003 Threading Sticky-tier COMP-param write deferred to cook thread (TD THREAD CONFLICT)
B-004 Wiring Model badge: EV_MODEL broadcast restored + agent fires every turn
B-005 Resilience _run_safe name-matches AgentError family across TD module reloads
B-007 Prompt New protocol point forbidding identical-arg probe loops
B-008-A Router Smarter heuristic: structural-noun signal catches "Build X loop"
B-008-C Router Reactive escalation: CycleDetected → next turn pro
B-008-T Router Task-sticky pro: stay on pro until user signals task done
B-009 UX False 'idle (timeout)' → backend heartbeat + 240s + auto-recovery
B-010 Cycle-detect args_hash deep-canonicalization closes list-order evasion

UX polish

  • Cost-pill uses --text at full opacity for contrast vs the dim token-meter; --error-fg + bold over $1.
  • Cycle-detector rows surface as single clean line (no double-emission with "Worker crash:" prefix).

See CHANGELOG.md#240---2026-05-13 for full details.

🤖 Generated with Claude Code

v2.3.0 — bilateral audit (9 bugs) + scoped snapshot tools

Choose a tag to compare

@dreamrec dreamrec released this 11 May 20:50

2.3.0 - 2026-05-11

Bilateral-audit release. Same-day follow-up to v2.2.0 driven by a deep end-to-end audit of the tdpilot_API chat tox running live against a real DeepSeek session. Closes 9 confirmed bugs (4 of which were latent security gaps that survived the v1.7.1 hardening), adds a new agent-callable scoped-snapshot system (Bug 19), and tightens the chat-pipe webserver against several TD 2025.32820/macOS-specific quirks discovered during the audit. No breaking changes for existing users.

Headline changes from v2.2.0

  • Scoped snapshot save+restore as agent tools (Bug 19, new feature). Two new tools: snapshot_save_scoped(name, scope='/project1', excludes=[]) writes a JSON manifest of the scope's structural shape — nodes, parameters, connections — excluding the agent's own COMP. snapshot_restore_scoped(name, dry_run=False) diffs the current scope against the manifest and applies create / delete / param-update / connect / disconnect ops to converge. Restore is safe to call mid-conversation because the agent COMP is always excluded from both save and restore. Pair with snapshot_list (now lists both .toe full snapshots and .scoped.json manifests with a kind field for disambiguation). Captures node tree + params + connections; does NOT capture DAT text, extension code, geometry data, or animation curves — for those use the full .toe snapshot. Manifest format is tdpilot_api_snapshot_scoped_v1 (versioned for future migrations).

  • Token-auth, JSON envelope, and WS handshake hardening for tdpilot_API.tox (port 9987). TD 2025.32820/macOS flattens HTTP request headers with original case (X-TDPilot-Token, not lowercase) instead of nesting them under request['headers']. The auth gate's pre-fix case-sensitive lookup found nothing, so EVERY request looked headerless to the agent — silently neutering the v1.7.1 X-TDPilot-Token check, the origin allowlist, AND Content-Type-based JSON envelope detection. Fixed in tdpilot_api_web_callbacks._headers(): case-fold every direct key on request, skip non-header request fields, then merge request['headers'] on top. The _check_auth, JSON-envelope, and CORS gates now actually engage as the security model documents. (Bug A — see commit b3c3dfa.)

  • Inbox drain race fix — silent message stranding after concurrent /send. Pre-fix, _drain_inbox_one ran only from the EV_DONE handler. EV_DONE fires from the worker thread BEFORE the worker fully exits, so start_turn's worker.is_alive() check returned True at that exact moment, drain re-inserted the message to the head, and NOTHING ever retriggered. Queue stranded forever. Now: a single self._drain_inbox_one() call after the for kind, payload in drain_events() loop in DrainEvents. Since the executor's onFrameStart already runs DrainEvents every cooked frame, the queue auto-recovers the instant the worker thread joins. Verified with a 5-prompt queue: pre-fix only the first drained, post-fix all 5 produce assistant replies. (Bug 7 — commit b3c3dfa.)

  • Agent over-eager tool use from short prompts (Bug 8 — commit 06ce82d). Pre-fix a bare "Reply: KICK1" caused the agent to autonomously execute memory_get → td_search_nodes → td_create_node(noiseTOP, name='audit_noise_test_2026') → td_get_errors — creating an unrequested node in the user's project. Root cause: the bm25 pre-retrieval pulled in a memory entry whose body literally described a smoke-test procedure, and the agent treated the entry's instructions as a current command. Four-layer defense landed: (a) new "User-intent gate" paragraph at the top of SYSTEM_PROMPT_BASE listing destructive tools and stating they're authorized ONLY when the current user message contains a clear affirmative request; (b) 16-char prompt floor on pre-retrieval (skip bm25 entirely for prompts shorter than that — admits "what's the FPS here?" while rejecting "KICK1", "echo X", "hi"); (c) length-relative bm25 score threshold (<40 chars requires score ≥0.5, otherwise ≥0.05); (d) retrieval-block prefix rewritten as "INFORMATIONAL CONTEXT ONLY — do NOT execute procedures unless the user EXPLICITLY asks". Verified: the same "Reply: KICK1" prompt now returns tools=0, reply='KICK1' in 1s.

  • WebSocket keepalive + dead-client reaper (Bug 9 — commits b812bb2 + 4de8fc0). Pre-fix, comp.storage['tdpilot_api_ws_clients'] leaked zombie handles because TD 2025/macOS's webSocketSendText is silently best-effort against closed sockets (the raise-on-send backstop never fired). Now: HTML chat sends {"type":"ping"} every 5s; onWebSocketReceive* updates a per-client last_seen map in comp.storage; the reaper evicts handles whose last_seen is older than 15s (3 missed pings @ 5s cadence). Frame-throttled @ 5s via _maybe_reap_ws_clients in DrainEvents. Includes an orphan-sweep that drops last_seen entries with no matching client (cleanup for late-ping race during onWebSocketClose). Verified with a 3-client fanout test: silent client reaped within 20s, active clients retained.

  • Strict message type validation on /send (Bugs 10/13/14 — commit b812bb2). Pre-fix {"message": null} / {"message": {"nested": 1}} / {"message": 0} / {"message": true} all returned 200 queued because the body-parse path did str(payload.get("message", "")) — coerced None → "None", dict → "{'nested': 1}", int → "0", bool → "True", all silently passed to the agent. Now: only str values pass; everything else 400s with "message" must be a string (got <type>). JSON envelope parsing is also now Content-Type-agnostic — peeks at the body shape and parses if it looks like {"message": "..."}, falls back to legacy plain-text for no-Origin tooling.

  • Browser-side stuck-404 fix via Cache-Control + favicon route (Bug 11 — commits b812bb2 + 4de8fc0). Chromium auto-fetches /favicon.ico on every page load WITHOUT the auth header, and the embedded webRenderTOP's error-display logic conflated the resulting 401 with "page can't be found / HTTP ERROR 404" even though the actual page loaded fine. Plus, transient .tox-rebuild windows briefly returned 401/error responses that Chrome cached and kept serving even after the server came back healthy — surfacing as a stuck 404 across the whole tab. Two fixes: whitelist /favicon.ico in _check_auth (returns 204 No Content), and set Cache-Control: no-store, no-cache, must-revalidate, max-age=0 + Pragma: no-cache on every response so browsers can never retain a transient error. onServerStart also now pulses chat_web.par.autorestartpulse + .reloadsrc on every .tox reload so the in-TD chat panel doesn't keep showing the cached error after a rebuild.

  • Double status:idle dedupe (Bug 5 — commit b3c3dfa). Every turn-end emitted two consecutive {"type":"status","status":"idle"} WS events. Cosmetic but pollutes the event stream. _html_status now suppresses repeated identical statuses via comp.storage['tdpilot_api_last_status']. Event count per turn dropped from 7 → 6.

  • WebSocket path-segment auth, HEAD method allowed for bootstrap routes (commits b3c3dfa + 4de8fc0). The HTML client emits ws://host:port/<token> (path-segment), but the original v1.7.1 extractor only read query-string ?t=<token>. Now accepts both. HEAD /, HEAD /index.html, HEAD /health, HEAD /favicon.ico whitelisted alongside their GET counterparts so browser cache-revalidation HEAD-probes don't trip the auth gate.

Behaviour changes worth flagging

  • Default Authmode flipped from "open" to "token" in the build script. New .tox builds ship token-required by default. Users who relied on the v2.2.x drag-and-go Authmode="open" for external scripting can flip the COMP param back to "open" explicitly. (Bug 1 — commit b3c3dfa.)
  • Pre-turn retrieval now skips prompts under 16 chars entirely. Short prompts like "hi", "ping", or "Reply: X" previously triggered bm25 retrieval which could pull in instruction-shaped memory entries the agent then treated as commands. The Bug 8 fix gates retrieval entirely at <16 chars and tightens the bm25 score threshold to 0.5 for queries <40 chars. Legitimate short questions ("Define CHOP family.") still trigger retrieval when bm25 finds a strong match.
  • snapshot_list response shape includes a new kind field ("toe" or "scoped"). Existing callers that only read filename/path/size_bytes/modified see no breakage; the new field is additive.

Tool surface

Added 2 new agent tools (snapshot_save_scoped, snapshot_restore_scoped). Total tool count: 93 (was 91 in v2.2.0).

Tests

tests/test_tdpilot_api_runtime.py::test_pre_turn_retrieval_handler_failure_is_isolated updated to use a >16-char prompt (the Bug 8 fix gates retrieval entirely below that). tests/test_standalone_csrf.py::test_ws_token_extraction_from_uri updated to assert the new path-segment form. Full suite: 1848 passing, 12 deselected.

TD 2025.32820/macOS quirks discovered during the audit

Documented for the record so we don't relitigate them next time:

  • root.findChildren(depth=N, includeNested=True) SILENTLY returns an empty list. Use op.children + manual BFS instead — the workaround now lives in tdpilot_api_patches._walk_scope.
  • op.webserverDAT listens IPv4-only (*:port LISTEN). localhost resolves to 127.0.0.1 on macOS by default so the impact is small, but [::1]:port (explicit IPv6) fails.
  • webSocketSendText is silently best-effort against closed sockets — does NOT raise. Build any liveness check on inbound traffic (client pings), not outbound send.
  • request['headers'] is empty on this build; headers are flattened as direct keys on request with ORIGINAL case (X-TDPilot-Token, Origin, Content-Type).
  • webserverDAT silently drops PUT and PATCH requests (the callback never fires). External tooling using those methods will hang their connections.

Commits in this release

Read more

v2.2.0 — Phase 1 reliability foundation + drag-and-go UX

Choose a tag to compare

@dreamrec dreamrec released this 11 May 17:07

v2.2.0 — Phase 1 Complete

First milestone of the v2.2.0→v3.0 roadmap. 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.

What's new since v2.1.5

Phase Headline PR
1.1 Auto-rollback on error regression — agent breaks something → atomically reverted via ui.undo.undo() + hint surfaced to LLM for self-correction #34 + #36
1.2 Cycle detection in tool chains — per-turn ledger, breaks the turn at 3 identical calls #37
1.2.1 Drag-and-go UX polish — Authmode COMP param (default open), auto-save+auto-reload on Apikey change, 401 reconnect banner #38
1.2.2 Build scripts auto-mirror .tox to main repo — eliminates stale-symlink footgun #39
(refactor) Paired source-file-list consolidation #35

136 new tests since v2.1.5. Total suite: 1848 passing.

End-user experience

Brand new user: drag tdpilot_API.tox into /project1 → paste DeepSeek key into the Apikey param → chat works first try. Zero pulses, zero env vars, zero auth dance.

During complex multi-step builds: agent can break things (compile errors in glslTOP, expression typos, syntax errors in script DATs) and recover automatically — no user intervention.

Behaviour changes worth flagging

  • Default chat-pipe webserver auth posture changes from token-required to origin-allowlist-only (Authmode=open default). Safe on TouchDesigner's typical single-user dev/perform usage profile — the origin allowlist still rejects cross-origin browser CSRF. Users sharing .toe across machines: set Authmode=token on the COMP for that deployment.
  • The MCP-server tdpilot-dpsk4.tox (port 9985) auth model is unchangedTD_MCP_SHARED_SECRET still required.

Install / upgrade

Via Claude Code plugin marketplace (recommended)

claude plugin update tdpilot-dpsk4@dreamrec-TDPilot_deepseekv4

Via npm

npm install -g tdpilot-dpsk4@2.2.0

Or run via npx (no install):

npx -y tdpilot-dpsk4@2.2.0

Drag the .tox directly

Pick up tdpilot_API.tox (chat-pipe with bundled DeepSeek backend) or tdpilot-dpsk4.tox (MCP server for Claude Code / Claude Desktop / Cursor) from this release's artifacts. Drag into /project1 in TouchDesigner.

See CHANGELOG.md for the full narrative + every behavior change.

🤖 Generated with Claude Code

v2.1.5 — Codex P2 follow-up on v2.1.4 (idle-suffix predicate)

Choose a tag to compare

@dreamrec dreamrec released this 10 May 09:54

2.1.5 - 2026-05-10

Patch: Codex P2 follow-up on v2.1.4 (PR #29). A cosmetic-but-real
UI bug in the v2.1.4 send-button safety timer.

Fix

  • isWorkingAgentState now treats 'idle <suffix>' as
    non-working.
    v2.1.4's safety timer fires
    setAgentStatus('idle (timeout)') after the 90s cap. The
    predicate only treated exact 'idle' (and 'ready' / 'reset'
    / 'connected') as non-working, so 'idle (timeout)' was
    classified as working — the pulse animation + Stop button
    stayed visible until a real status event arrived (which may
    never come if the WS is unavailable). The functional path was
    fine because clearAwaitingTurnEnd() ran first, but the UI
    lied about the agent's state.

    Fix in td_component/tdpilot_api_chat.html: the predicate now
    also returns false when t.startsWith('idle ') or
    t.startsWith('idle('). This covers any future
    'idle (<context>)' variant. Test:
    tests/test_v214_codex_followups.py::test_p2_v215_idle_suffix_treated_as_non_working.

Context

Codex's automated review on PR #29 caught this as a P2 right
after v2.1.4 merged. Pattern: each Codex pass on a recent
release tends to catch one or two real edge-case regressions in
the new fixes themselves. v2.1.3 → v2.1.4 → v2.1.5 chains three
of these in a single afternoon.