Files
deepseek-harness/packages/context/tmux-context
Turtle 44a657c149 fix(tmux-context): contain executor rejection as a warning, correct suppression claim
Review round on #758.

bash.run() only promises to resolve for nonzero exits, timeouts, and aborts,
and bash.resolve() can reject on policy grounds, so either could escape the
serial agent/step listener and abort the model turn — contradicting the
plugin's documented failed-query no-op contract. Contain both and log a
warning instead; the location is optional context.

The Agent Note claimed an unchanged location suppresses the query. It does
not: only the interval floor is checked before the query, while change
suppression compares state the query returned. Corrected in both languages
and re-recorded the i18n pairs.
2026-07-29 11:11:17 +08:00
..

@deepseek-ai/dsh-tmux-context

English | 中文

Opt-in durable context naming the tmux session, window, and pane this agent process runs in, plus the window's pane-tree layout. Sampled once per turn during model-request preparation. dsh-agent-spine-demo and shipped examples do not mount it. Decision record: the tmux-context Agent Note.

Config

- id: tmux-context
  name: '@deepseek-ai/dsh-tmux-context'
  config:
    refreshIntervalMs: 60000 # optional; omit or set to 0 to inject on every changed turn

refreshIntervalMs must be a non-negative safe integer. Omission or 0 injects whenever the tmux state changed since the last injection. A positive value additionally suppresses injections that fall within that many milliseconds of the latest one.

How it reads tmux

The plugin prepends an agent/step listener that runs only on the first step of each turn. When due, it runs one read-only command through the ctx.bash executor seam:

[ -n "$TMUX_PANE" ] || exit 1
self_tty=$(ps -o tty= -p <pid> | tr -d ' ')
pane_tty=$(tmux display-message -t "$TMUX_PANE" -p '#{pane_tty}') || exit 1
[ "$pane_tty" = "/dev/$self_tty" ] || exit 1
exec tmux display-message -t "$TMUX_PANE" -p '<format>'

$TMUX_PANE alone is insufficient: a terminal launched from a tmux shell (a VS Code integrated terminal, a desktop launcher) inherits $TMUX and $TMUX_PANE from that ancestor, so the variables are present even though the process does not live in that pane. The command therefore also compares the pane's #{pane_tty} against this process's own controlling terminal (ps -o tty= for its pid): a genuine pane owns this process's tty, while an inherited environment names some other pane's tty. Running through ctx.bash applies the deployment's sandbox and policy; the plugin owns no subprocess code. When ctx.bash is absent, the process is not in a real tmux pane ($TMUX_PANE unset, or the tty does not match ⇒ nonzero exit), or the reading is malformed, the attempt is a no-op, never an error. The location is optional, so an executor rejection — a policy refusal from resolve() or an infrastructure failure from run() — is contained and logged as a warning rather than failing the turn.

State is pulled on every eligible turn — a moved, renamed, or re-laid-out pane is picked up without any tmux hook or background process. The plugin re-injects only when the rendered tmux state differs from its last injection, so an unchanged location adds nothing.

Timing semantics

When an injection is due, the plugin appends one injected user/message through agent.inject() before step/start, with source { kind: 'plugin', plugin: 'tmux-context' }. Change suppression and interval scheduling scan the raw durable session events for the latest injection of this source, so the schedule survives compaction and resumed processes without process-local cache state; sessions schedule independently. The reading records a request-preparation attempt, not a committed step; because the listener runs first, its append may remain when a later pre-step listener cancels or fails the attempt (the log is append-only and the plugin performs no rollback).

Model Experience

Preparation-time tmux location

What the model sees

On each turn whose tmux state changed, one source-tagged context message with the three lines below. <window-layout> is tmux's compact pane-tree description; pane and window pixel sizes are intentionally excluded, and the contents of sibling panes are never captured.

Changed-turn reading
tmux location (turn <turn>):
session <session>, window <index> "<name>", pane <index> <pane-id>
window active=<0|1>, pane active=<0|1>, layout <window-layout>

Token effect

Each two-line reading accumulates until compaction shadows it. Unchanged locations and interval suppression add nothing.

KV Cache effect

Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.

Known Limitations and Deferred Work

  • First step only — a pane moved or resized mid-turn is reflected on the next turn, not between steps.
  • Own location only — the plugin never captures the visible text of sibling panes.
  • Layout, not size — pane/window pixel dimensions are omitted; only the layout tree and active flags are reported.
  • Tab-delimited fields — a tmux window name containing the literal two-character sequence \t would mis-split the reading and be skipped as malformed; ordinary names are unaffected.
  • tty-based pane detection — the process is considered "in tmux" only when its controlling terminal matches $TMUX_PANE's #{pane_tty}. This deliberately excludes terminals that inherited $TMUX/$TMUX_PANE from a tmux ancestor (e.g. a VS Code integrated terminal). ps -o tty= is POSIX; the check is a no-op wherever it or #{pane_tty} is unavailable.