Files
deepseek-harness/packages/context/tmux-context
Huanqi Cao 3d1166fcdd feat(bash): shell tools reject a mismatched executor dialect at load
The seam gains ShellDialect ('bash' | 'powershell' - concrete shells, not
families: zsh or fish would be their own values, never 'bash'); bash-local
declares bash (bash-sandbox inherits), pwsh-local declares powershell, and
both tools throw at load when the mounted executor speaks another dialect -
previously tool-pwsh over bash-local handed PowerShell text to bash -c and
the deployment error surfaced as ordinary nonzero exits. Pinned by mismatch
tests on both tools; the parity note records the contract (both languages).

Also from the review round: the tool-bash README's managed-environment
section becomes a summary linking the owning dsh-bash-env contract (the
duplicated prose carried a stale owner in its example import), the
pwshOnly JSDoc drops the stale 'on PATH' phrasing, and the task-tools
contract comment in the two pwsh compositions is indented into its block.
2026-08-03 22:47:53 +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. The shipped TUI mounts it; dsh-agent-spine-demo and the Web/headless surfaces do not. 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.