|
6 | 6 | _Notes on upcoming releases will be added here_ |
7 | 7 | <!-- END PLACEHOLDER - ADD NEW CHANGELOG ENTRIES BELOW THIS LINE --> |
8 | 8 |
|
9 | | -### Features |
10 | | - |
11 | | -- {tooliconl}`respawn-pane` gains an ``environment`` parameter |
12 | | - (``dict[str, str]``) that maps to tmux's ``respawn-pane -e |
13 | | - KEY=VALUE`` flag (one ``-e`` per entry, single-arg ``-e<KEY>=<VAL>`` |
14 | | - form to mirror the upstream emitter). Closes the parity gap with |
15 | | - ``Pane.respawn(environment=)`` on libtmux's ``tmux-parity`` branch. |
16 | | - The audit-log redaction policy is extended to recognise dict-shaped |
17 | | - sensitive args: each value is replaced by a ``{len, sha256_prefix}`` |
18 | | - digest while keys (env var names like ``DATABASE_URL``) remain |
19 | | - visible — keys are operator-debug-useful, values are the secret. |
20 | | - Note: like ``shell``, env var values may briefly appear in the OS |
21 | | - process table before the spawned shell inherits them; do not pass |
22 | | - long-lived secrets when other tenants on the host could observe |
23 | | - ``ps``. |
24 | | - |
25 | | -### Tests |
26 | | - |
27 | | -- New ``test_registered_tools_accept_socket_name`` introspection test |
28 | | - in ``tests/test_server.py`` enumerates every registered tool via |
29 | | - ``FastMCP.list_tools()`` and asserts each accepts a ``socket_name`` |
30 | | - parameter, with ``list_servers`` as the documented exception (it |
31 | | - takes ``extra_socket_paths`` instead). Catches future tool drift |
32 | | - before it silently makes ``_BASE_INSTRUCTIONS`` lie to agents. The |
33 | | - ``_BASE_INSTRUCTIONS`` text itself is tightened from "All tools |
34 | | - accept an optional socket_name parameter" to "Targeted tmux tools |
35 | | - accept an optional socket_name parameter (defaults to LIBTMUX_SOCKET |
36 | | - env var); list_servers discovers sockets via TMUX_TMPDIR plus |
37 | | - optional extra_socket_paths instead." A companion content test |
38 | | - (``test_base_instructions_document_socket_name_contract``) locks the |
39 | | - wording. |
40 | | - |
41 | | -### Scripts |
42 | | - |
43 | | -- ``scripts/mcp_swap.py``: ``use-local`` now preserves an existing |
44 | | - entry's ``env`` dict on replacement. Previously a swap silently |
45 | | - dropped client-side environment settings (``LIBTMUX_SAFETY``, |
46 | | - ``LIBTMUX_SOCKET``, custom dev knobs) because ``build_local_spec`` |
47 | | - returned a spec with empty env and ``set_server`` wrote it unchanged. |
48 | | - The timestamped backup at ``.bak.mcp-swap-<ts>`` still captures the |
49 | | - full original config for full recovery via ``revert``, but day-to-day |
50 | | - swaps no longer require manual env restoration. The merge mirrors |
51 | | - ``_spec_from_entry`` (which round-trips env on the read side). |
52 | | - Regression test: |
53 | | - ``tests/test_mcp_swap.py:test_use_local_preserves_existing_env_when_replacing`` |
54 | | - plus a paired ``test_use_local_with_no_prior_entry_writes_empty_env`` |
55 | | - to lock the Codex "added" path against accidental env synthesis. |
56 | | -- ``scripts/mcp_swap.py``: ``_claude_project_node`` validates that |
57 | | - Claude's undocumented ``projects.<abs>.mcpServers`` layout is still |
58 | | - mapping-shaped before mutating it. If a future Claude release |
59 | | - reshapes the structure, the script raises ``RuntimeError("Claude |
60 | | - config layout appears to have changed...")`` *before* the atomic |
61 | | - write — so the timestamped backup defense isn't asked to recover |
62 | | - from a partial mutation. Three new tests in ``tests/test_mcp_swap.py`` |
63 | | - cover the rejection paths and the well-shaped happy path. |
64 | | -- ``scripts/mcp_swap.py``: ``_claude_project_node`` now uses |
65 | | - ``@t.overload`` so ``create=True`` is statically narrowed to |
66 | | - ``dict[str, t.Any]``. ``set_server`` drops the runtime |
67 | | - ``assert node is not None`` (which would have been stripped under |
68 | | - ``python -O``) — mypy proves the invariant via the overload instead. |
69 | | - Strict-typing-priority alignment. |
70 | | -- ``scripts/mcp_swap.py`` PEP 723 ``requires-python`` lowered from |
71 | | - ``>=3.11`` to ``>=3.10`` to match the project floor in |
72 | | - ``pyproject.toml``. The script does not use any 3.11-only features |
73 | | - (verified: no ``tomllib``, ``ExceptionGroup``, ``except*``, or |
74 | | - structural ``match``); the previous bound made ``uv run scripts/ |
75 | | - mcp_swap.py`` provision a fresh 3.11 toolchain on contributor |
76 | | - machines that otherwise share the project's 3.10 venv. |
77 | | - |
78 | | -### Docs |
79 | | - |
80 | | -- {ref}`safety` macOS ``TMUX_TMPDIR`` caveat now reflects shipped |
81 | | - behaviour. ``_effective_socket_path`` already queries tmux's |
82 | | - ``display-message -p '#{socket_path}'`` first and only falls back to |
83 | | - ``$TMUX_TMPDIR`` reconstruction when the server is unreachable, but |
84 | | - the doc still framed the structural fix as future work and told |
85 | | - operators to set ``TMUX_TMPDIR`` explicitly. Replace with the actual |
86 | | - three-step resolution order so doc readers don't write code or |
87 | | - service files chasing a problem that's already solved. |
88 | | -- Strip dangling internal-audit citations from production source |
89 | | - comments. ``window_tools.py:106`` no longer references "the |
90 | | - brainstorm-and-refine audit §7.1" — the architectural fence |
91 | | - (the four-hierarchy-level rule) stays, the orphaned citation goes. |
92 | | - ``session_tools.py:74-76`` cross-reference still resolves. The |
93 | | - ``hook_tools.py`` module docstring drops "The brainstorm-and-refine |
94 | | - plan deliberately excludes write-hooks" and keeps the rationale |
95 | | - (hook persistence + lifespan teardown gap). |
96 | | -- {tooliconl}`get-window-info` "Avoid when" guidance now points to |
97 | | - {tooliconl}`list-windows` (not {tooliconl}`list-panes`) for whole- |
98 | | - session window enumeration. The previous wording trained agents into |
99 | | - the wrong tool: ``list_panes`` returns ``PaneInfo`` objects, while |
100 | | - ``list_windows`` is the window enumerator and accepts ``session_id``. |
101 | | -- {tooliconl}`respawn-pane` docstring and topic page now include a tip |
102 | | - to call {tooliconl}`get-pane-info` first if the agent needs to |
103 | | - preserve ``pane_current_command`` across the respawn — tmux's default |
104 | | - behaviour is to replay the original argv, but a custom split-time |
105 | | - shell may differ. |
106 | | -- {ref}`safety` adds a {tooliconl}`respawn-pane` subsection under |
107 | | - "Footguns inside the `mutating` tier" alongside {tooliconl}`pipe-pane` |
108 | | - and {tooliconl}`set-environment`. Documents the `kill=True` default, |
109 | | - the non-idempotent retry semantics, the explicit-`pane_id` |
110 | | - requirement, and the `pane_current_command` / OS-process-table |
111 | | - visibility window for any `shell` argument. The per-tool annotation |
112 | | - table picks up a `respawn-pane` row showing the unusual mutating-tier |
113 | | - + `destructiveHint=true` + `idempotentHint=false` combination. |
| 9 | +### What's new |
114 | 10 |
|
115 | | -### API decisions (pre-release) |
| 11 | +#### New tool: `respawn_pane` |
116 | 12 |
|
117 | | -- {tooliconl}`respawn-pane` parameter ``shell_command`` is renamed to |
118 | | - ``shell`` to align with {tooliconl}`split-window` and the upstream |
119 | | - ``Pane.respawn(shell=)`` signature on libtmux's ``tmux-parity`` |
120 | | - branch. Settled before the tool reached ``origin/main`` so no |
121 | | - compatibility alias is shipped — alignment is the load-bearing |
122 | | - reason, not a fix to a previously released name. |
123 | | -- {tooliconl}`respawn-pane` signature drops the optional |
124 | | - ``session_name`` / ``session_id`` / ``window_id`` resolver fallbacks |
125 | | - that sibling pane tools accept. The runtime guard would have raised |
126 | | - on any combination missing ``pane_id`` anyway (the tool requires |
127 | | - explicit ``pane_id`` to prevent silent-kill-via-resolver), so the |
128 | | - parameters were dead surface area broadcasting targeting flexibility |
129 | | - to LLMs that did not exist. Validation now lives at the FastMCP |
130 | | - schema boundary via ``pane_id: str`` rather than an in-tool |
131 | | - ``ToolError`` raise. Settled before the tool reached ``origin/main`` |
132 | | - so no compat alias ships. |
133 | | - |
134 | | -### Refactor |
135 | | - |
136 | | -- Drop redundant ``["-t", pane.pane_id]`` argument prefixes at five |
137 | | - ``pane.cmd(...)`` sites (`pane_tools/lifecycle.py:132`, |
138 | | - `copy_mode.py:58, 110`, `meta.py:64, 150`). libtmux's ``Pane.cmd`` |
139 | | - already injects ``-t self.pane_id`` via ``Server.cmd``, so the |
140 | | - resulting wire form was ``tmux <verb> -t %X -t %X ...``; tmux's args |
141 | | - parser kept the last ``-t`` so behaviour was identical, but the |
142 | | - redundancy was confusing slop. The convention exemplar at |
143 | | - `copy_mode.py:60-66` (``pane.cmd("send-keys", "-X", ...)``) shows the |
144 | | - intended call shape. |
| 13 | +Restart a wedged pane in place — preserves `pane_id` and the window layout (the alternative `kill_pane` + `split_window` invalidates pane references and rearranges the window). Required `pane_id`; optional `kill` (default true), `shell`, `start_directory`, `environment`. Refuses to respawn the MCP server's own pane, mirroring the existing `kill_*` self-guards. Annotations honestly mark it `destructiveHint=true` / `idempotentHint=false` while staying in the `mutating` tier so default-profile agents can use it for shell recovery. Audit log redacts the `shell` argument and digests `environment` values (keys stay visible). (#27) |
145 | 14 |
|
146 | | -### Fixes |
| 15 | +#### New tools: `get_session_info`, `get_window_info` |
| 16 | + |
| 17 | +Targeted single-object metadata reads — no more `list_*` + filter when you have an ID. Completes the four-level `get_*_info` hierarchy (server / session / window / pane); buffers, hooks, and options have existing read paths and are deliberately excluded. (#27) |
| 18 | + |
| 19 | +#### New dev script: `scripts/mcp_swap.py` |
| 20 | + |
| 21 | +Point Claude / Codex / Cursor / Gemini at a local checkout in one command. `just mcp-use-local` rewrites each CLI's global config to run the local repo via `uv`; `just mcp-revert` restores from a timestamped backup. `just mcp-detect` and `just mcp-status` report install state. Atomic writes, `--dry-run` mode, per-CLI state file, env preservation on replacement, layout-shape guard before mutating Claude's per-project config. Scope is global configs only — see `scripts/README.md`. (#27) |
| 22 | + |
| 23 | +### Documentation |
| 24 | + |
| 25 | +- {ref}`safety` adds a {tooliconl}`respawn-pane` "Footgun" subsection alongside {tooliconl}`pipe-pane` and {tooliconl}`set-environment`: `kill=true` default, non-idempotent retries, explicit-`pane_id` requirement, OS-process-table visibility for `shell` / `environment`. The macOS `TMUX_TMPDIR` caveat is rewritten to reflect shipped behaviour — tmux's three-step socket-path resolution is documented, so operators no longer need to set `TMUX_TMPDIR` explicitly to chase a problem that's already solved. (#27) |
| 26 | +- LLM-facing discoverability tightened: {tooliconl}`display-message` retitled "Evaluate tmux Format String" with a docstring that leads with read-only format expansion (the tool wraps `display-message -p` but `-p` expands rather than displays), {tooliconl}`pipe-pane` leads with the `/tmp/pane.log` logging use case, and the server instructions explain why hooks are read-only and why there is no `list_buffers` tool. The `socket_name` contract is tightened to acknowledge {tooliconl}`list-servers` as the documented exception. (#27) |
| 27 | +- Topic pages added for {tooliconl}`respawn-pane`, {tooliconl}`get-session-info`, and {tooliconl}`get-window-info`. (#27) |
| 28 | + |
| 29 | +### API decisions (pre-release) |
147 | 30 |
|
148 | | -- Audit log now redacts the ``shell`` argument on |
149 | | - {tooliconl}`respawn-pane` (and ``content`` on {tooliconl}`load-buffer`, |
150 | | - which the code already redacted but the docs did not list). The |
151 | | - ``shell`` payload may carry credentials passed to a relaunched |
152 | | - process; redacting the MCP audit log keeps them out of long-lived |
153 | | - log archives. Note: ``shell`` may still appear briefly in the OS |
154 | | - process table and tmux's ``pane_current_command`` metadata until the |
155 | | - spawned shell takes over — do not pass credentials directly even |
156 | | - with redaction. |
157 | | -- {tooliconl}`respawn-pane` now requires an explicit ``pane_id``. Its |
158 | | - signature still accepts ``session_name`` / ``session_id`` / |
159 | | - ``window_id`` for backwards-compatibility with the shared pane-target |
160 | | - resolution surface, but a runtime guard raises before |
161 | | - ``_resolve_pane`` is invoked. Without the guard, calling |
162 | | - ``respawn_pane(session_name="dev")`` resolved through ``_resolve_pane`` |
163 | | - to the first pane of the first window — combined with default |
164 | | - ``kill=True`` that could silently kill a critical running process |
165 | | - (e.g. an `npm run dev` server) instead of the intended wedged shell. |
166 | | - Resolve the target via {tooliconl}`list-panes` first. |
167 | | -- {tooliconl}`respawn-pane` now advertises honest MCP annotations. |
168 | | - Previously the tool inherited ``ANNOTATIONS_MUTATING`` defaults |
169 | | - (`destructiveHint=False`, `idempotentHint=True`) even though its |
170 | | - default `kill=True` sends `SPAWN_KILL` to the running process and |
171 | | - repeated calls kill repeated processes. The new |
172 | | - `ANNOTATIONS_MUTATING_DESTRUCTIVE` preset keeps the tool in |
173 | | - `TAG_MUTATING` (so it stays visible to default-profile agents for |
174 | | - shell recovery) while exporting `destructiveHint=True` and |
175 | | - `idempotentHint=False`. Agents reading the annotations can no longer |
176 | | - conclude that respawn-retries are safe. |
| 31 | +- {tooliconl}`respawn-pane` settles on `shell` (renamed from `shell_command`) to align with {tooliconl}`split-window` and upstream `Pane.respawn(shell=)`, and drops the `session_name` / `session_id` / `window_id` resolver fallbacks — the runtime guard rejected any call missing `pane_id` anyway. Validation now lives at the FastMCP schema boundary. (#27) |
177 | 32 |
|
178 | 33 | ## libtmux-mcp 0.1.0a3 (2026-04-19) |
179 | 34 |
|
|
0 commit comments