@dalmasonto/taskflow-mcp 1.0.33 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -193
- package/dist/attachment-download.d.ts +74 -0
- package/dist/attachment-download.js +193 -0
- package/dist/attachment-download.js.map +1 -0
- package/dist/attachments.d.ts +23 -0
- package/dist/attachments.js +66 -0
- package/dist/attachments.js.map +1 -0
- package/dist/client.d.ts +206 -0
- package/dist/client.js +279 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +137 -18
- package/dist/config.js +187 -106
- package/dist/config.js.map +1 -0
- package/dist/connect.d.ts +89 -0
- package/dist/connect.js +269 -0
- package/dist/connect.js.map +1 -0
- package/dist/doctor.d.ts +24 -0
- package/dist/doctor.js +120 -0
- package/dist/doctor.js.map +1 -0
- package/dist/events.d.ts +186 -0
- package/dist/events.js +407 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +124 -184
- package/dist/index.js.map +1 -0
- package/dist/instructions.d.ts +12 -0
- package/dist/instructions.js +114 -0
- package/dist/instructions.js.map +1 -0
- package/dist/mint.d.ts +62 -0
- package/dist/mint.js +135 -0
- package/dist/mint.js.map +1 -0
- package/dist/mirror.d.ts +68 -0
- package/dist/mirror.js +103 -0
- package/dist/mirror.js.map +1 -0
- package/dist/pane-queue.d.ts +29 -0
- package/dist/pane-queue.js +35 -0
- package/dist/pane-queue.js.map +1 -0
- package/dist/prompts.d.ts +79 -0
- package/dist/prompts.js +211 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resolve.d.ts +72 -0
- package/dist/resolve.js +89 -0
- package/dist/resolve.js.map +1 -0
- package/dist/runtime.d.ts +54 -0
- package/dist/runtime.js +321 -0
- package/dist/runtime.js.map +1 -0
- package/dist/server.d.ts +56 -0
- package/dist/server.js +793 -0
- package/dist/server.js.map +1 -0
- package/dist/session-identifier.d.ts +48 -0
- package/dist/session-identifier.js +44 -0
- package/dist/session-identifier.js.map +1 -0
- package/dist/sessions-store.d.ts +38 -0
- package/dist/sessions-store.js +88 -0
- package/dist/sessions-store.js.map +1 -0
- package/dist/tmux.d.ts +200 -0
- package/dist/tmux.js +580 -0
- package/dist/tmux.js.map +1 -0
- package/hooks/metadata.mjs +99 -0
- package/hooks/permission-prompt.mjs +100 -0
- package/hooks/taskflow-hook.mjs +499 -0
- package/hooks/tool-logging.mjs +63 -0
- package/package.json +38 -29
- package/dist/agent-registry.d.ts +0 -27
- package/dist/agent-registry.js +0 -153
- package/dist/db.d.ts +0 -5
- package/dist/db.js +0 -220
- package/dist/helpers.d.ts +0 -21
- package/dist/helpers.js +0 -27
- package/dist/resources.d.ts +0 -2
- package/dist/resources.js +0 -89
- package/dist/sse.d.ts +0 -10
- package/dist/sse.js +0 -766
- package/dist/tmux-bridge.d.ts +0 -12
- package/dist/tmux-bridge.js +0 -157
- package/dist/tools/activity.d.ts +0 -39
- package/dist/tools/activity.js +0 -152
- package/dist/tools/agent-inbox.d.ts +0 -12
- package/dist/tools/agent-inbox.js +0 -257
- package/dist/tools/agent.d.ts +0 -14
- package/dist/tools/agent.js +0 -168
- package/dist/tools/analytics.d.ts +0 -21
- package/dist/tools/analytics.js +0 -191
- package/dist/tools/checkpoint.d.ts +0 -27
- package/dist/tools/checkpoint.js +0 -105
- package/dist/tools/notifications.d.ts +0 -31
- package/dist/tools/notifications.js +0 -59
- package/dist/tools/projects.d.ts +0 -55
- package/dist/tools/projects.js +0 -112
- package/dist/tools/settings.d.ts +0 -19
- package/dist/tools/settings.js +0 -73
- package/dist/tools/tasks.d.ts +0 -105
- package/dist/tools/tasks.js +0 -403
- package/dist/tools/terminal.d.ts +0 -4
- package/dist/tools/terminal.js +0 -98
- package/dist/tools/timer.d.ts +0 -37
- package/dist/tools/timer.js +0 -154
- package/dist/types.d.ts +0 -82
- package/dist/types.js +0 -30
package/dist/tmux.js
ADDED
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `taskflow-mcp --tmux [target]` — mirror a tmux pane into TaskFlow so humans
|
|
3
|
+
* can watch what an agent is doing.
|
|
4
|
+
*
|
|
5
|
+
* WHY A MIRROR AND NOT A LOG. The obvious design is to tail new output and append
|
|
6
|
+
* it as frames. That does not work for the case this exists for: a full-screen
|
|
7
|
+
* TUI agent (Claude Code itself) runs on tmux's ALTERNATE SCREEN buffer, which
|
|
8
|
+
* has no scrollback — `history_size` stays flat no matter how much scrolls by,
|
|
9
|
+
* so there is no stream of new lines to tail. The only observable state is the
|
|
10
|
+
* current screen, redrawn in place. So each capture is sent as a `snapshot`
|
|
11
|
+
* frame that REPLACES the view rather than appending to it.
|
|
12
|
+
*
|
|
13
|
+
* Sends only when the screen actually changed, so an idle agent costs one cheap
|
|
14
|
+
* capture per tick and no writes.
|
|
15
|
+
*
|
|
16
|
+
* By default the pane is only ever READ (`capture-pane`) — nothing is injected and
|
|
17
|
+
* no tmux option is altered, so mirroring cannot disturb the session. `--notify`
|
|
18
|
+
* opts in to WRITING: a one-line unread notice typed into the pane. See
|
|
19
|
+
* {@link buildNotice} for why that text is composed locally and never by the
|
|
20
|
+
* server.
|
|
21
|
+
*/
|
|
22
|
+
import { execFile } from "node:child_process";
|
|
23
|
+
import { hostname } from "node:os";
|
|
24
|
+
import { promisify } from "node:util";
|
|
25
|
+
import { TaskflowClient } from "./client.js";
|
|
26
|
+
import { loadProfile } from "./config.js";
|
|
27
|
+
const run = promisify(execFile);
|
|
28
|
+
/**
|
|
29
|
+
* How often the pane is CAPTURED. This is a local `tmux capture-pane` — a few
|
|
30
|
+
* milliseconds, no network — so it can be frequent without costing anything.
|
|
31
|
+
* What it costs to send is governed separately, below.
|
|
32
|
+
*/
|
|
33
|
+
export const DEFAULT_INTERVAL_MS = 1000;
|
|
34
|
+
/**
|
|
35
|
+
* Minimum gap between two frames on the wire.
|
|
36
|
+
*
|
|
37
|
+
* A working agent repaints constantly — spinners, elapsed-time counters, a
|
|
38
|
+
* cursor — so "the screen changed" is true almost every capture, and sending on
|
|
39
|
+
* every change would ship a full screen per tick forever. Changes are coalesced
|
|
40
|
+
* instead: at most one frame per second, and the LATEST capture wins, so the
|
|
41
|
+
* viewer sees current state rather than a queue of stale ones.
|
|
42
|
+
*/
|
|
43
|
+
const MIN_SEND_INTERVAL_MS = 1000;
|
|
44
|
+
/**
|
|
45
|
+
* How often to prove liveness when the screen is NOT changing.
|
|
46
|
+
*
|
|
47
|
+
* Appending a frame already bumps the session's `last_seen_at` server-side, so a
|
|
48
|
+
* streaming agent never needs a heartbeat. Only a quiet one does, and the
|
|
49
|
+
* liveness window is 90s — beating every couple of seconds was chatter for its
|
|
50
|
+
* own sake.
|
|
51
|
+
*/
|
|
52
|
+
const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
53
|
+
/** Backend cap is 20k chars; stay under it so a wide pane can't be rejected. */
|
|
54
|
+
const MAX_SNAPSHOT_CHARS = 18_000;
|
|
55
|
+
/**
|
|
56
|
+
* How much of a message notice may be TYPED into the pane.
|
|
57
|
+
*
|
|
58
|
+
* Larger than the status-line cap because this text is the agent's only view of
|
|
59
|
+
* an incoming message: it must carry the whole body plus the attachment
|
|
60
|
+
* manifest, or the agent answers half a request without knowing. Still bounded —
|
|
61
|
+
* it becomes literal keystrokes in a live session.
|
|
62
|
+
*/
|
|
63
|
+
const MAX_TYPED_NOTICE_CHARS = 1_200;
|
|
64
|
+
/**
|
|
65
|
+
* Build the notice typed into the agent's pane.
|
|
66
|
+
*
|
|
67
|
+
* The text is composed HERE, locally, from counts — never from anything the
|
|
68
|
+
* server sends. That is the whole security posture of this feature: the backend
|
|
69
|
+
* cannot dictate keystrokes into a live agent session, because it never supplies
|
|
70
|
+
* the string. Message bodies are deliberately excluded; the agent fetches them
|
|
71
|
+
* through `check_messages`, which is auditable, rather than having them typed
|
|
72
|
+
* into its prompt by a poller.
|
|
73
|
+
*
|
|
74
|
+
* Control characters are stripped and the result is a single line: a stray
|
|
75
|
+
* newline would submit the prompt early, and an escape sequence could rewrite
|
|
76
|
+
* the pane.
|
|
77
|
+
*/
|
|
78
|
+
export function buildNotice(unread, channels) {
|
|
79
|
+
const where = channels > 1 ? `${channels} channels` : "a channel";
|
|
80
|
+
return sanitizeForPane(`[taskflow] ${unread} unread message${unread === 1 ? "" : "s"} in ${where} — ` +
|
|
81
|
+
`call check_messages, then mark_read when handled.`);
|
|
82
|
+
}
|
|
83
|
+
/** One line, printable characters only — see {@link buildNotice}. */
|
|
84
|
+
export function sanitizeForPane(text, max = 240) {
|
|
85
|
+
// eslint-disable-next-line no-control-regex
|
|
86
|
+
// Strip whole ANSI escape sequences first: dropping only the ESC byte
|
|
87
|
+
// would leave its printable tail ("[2J") as visible noise. Then remove any
|
|
88
|
+
// remaining C0/C1 controls — a newline would submit the prompt early, and a
|
|
89
|
+
// bare ESC reads as the Escape KEY to a TUI, which can cancel its input.
|
|
90
|
+
const flat = text
|
|
91
|
+
// eslint-disable-next-line no-control-regex
|
|
92
|
+
.replace(/\x1B\[[0-?]*[ -\/]*[@-~]/g, " ")
|
|
93
|
+
// eslint-disable-next-line no-control-regex
|
|
94
|
+
.replace(/[\x00-\x1F\x7F-\x9F]+/g, " ")
|
|
95
|
+
.replace(/\s+/g, " ")
|
|
96
|
+
.trim();
|
|
97
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Find the tmux pane this agent is running in, with NO configuration.
|
|
101
|
+
*
|
|
102
|
+
* The MCP server is spawned by the agent (Claude Code), so it can work out where
|
|
103
|
+
* that agent lives rather than being told:
|
|
104
|
+
*
|
|
105
|
+
* 1. `$TMUX_PANE` — set by tmux and inherited straight down. Exact and free.
|
|
106
|
+
* 2. Otherwise match the ANCESTOR's controlling terminal to a pane's tty
|
|
107
|
+
* (v1's method): `readlink /proc/<pid>/fd/0` gives the pty, and
|
|
108
|
+
* `tmux list-panes` maps ptys to panes. We walk a few generations up because
|
|
109
|
+
* the MCP server may sit behind a shell wrapper, and only the agent process
|
|
110
|
+
* itself is attached to the pane's tty.
|
|
111
|
+
*
|
|
112
|
+
* Returns null when not under tmux, which is a normal state, not an error.
|
|
113
|
+
*/
|
|
114
|
+
export async function detectTmuxPane() {
|
|
115
|
+
const fromEnv = process.env.TMUX_PANE?.trim();
|
|
116
|
+
if (fromEnv)
|
|
117
|
+
return fromEnv;
|
|
118
|
+
let panes;
|
|
119
|
+
try {
|
|
120
|
+
const { stdout } = await run("tmux", ["list-panes", "-a", "-F", "#{pane_id} #{pane_tty}"]);
|
|
121
|
+
panes = stdout
|
|
122
|
+
.trim()
|
|
123
|
+
.split("\n")
|
|
124
|
+
.map((line) => {
|
|
125
|
+
const [id, tty] = line.split(" ");
|
|
126
|
+
return { id: id ?? "", tty: tty ?? "" };
|
|
127
|
+
})
|
|
128
|
+
.filter((p) => p.id && p.tty);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null; // no tmux server
|
|
132
|
+
}
|
|
133
|
+
if (!panes.length)
|
|
134
|
+
return null;
|
|
135
|
+
let pid = process.ppid;
|
|
136
|
+
for (let depth = 0; depth < 4 && pid && pid > 1; depth += 1) {
|
|
137
|
+
try {
|
|
138
|
+
const { stdout } = await run("readlink", [`/proc/${pid}/fd/0`]);
|
|
139
|
+
const tty = stdout.trim();
|
|
140
|
+
const hit = panes.find((p) => p.tty === tty);
|
|
141
|
+
if (hit)
|
|
142
|
+
return hit.id;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
/* pid gone, or no /proc (non-Linux) — try the next ancestor */
|
|
146
|
+
}
|
|
147
|
+
pid = await parentPid(pid);
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
/** The parent pid of `pid`, or undefined when it cannot be read. */
|
|
152
|
+
async function parentPid(pid) {
|
|
153
|
+
try {
|
|
154
|
+
const { stdout } = await run("ps", ["-o", "ppid=", "-p", String(pid)]);
|
|
155
|
+
const parsed = Number(stdout.trim());
|
|
156
|
+
return Number.isFinite(parsed) && parsed > 1 ? parsed : undefined;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** Is tmux installed and is a server running? */
|
|
163
|
+
export async function tmuxAvailable() {
|
|
164
|
+
try {
|
|
165
|
+
await run("tmux", ["list-panes", "-a", "-F", "#{pane_id}"]);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Capture a pane's visible screen as plain text. Deliberately WITHOUT `-e`, so
|
|
174
|
+
* tmux resolves colour/cursor escapes into rendered text — the transcript stores
|
|
175
|
+
* readable content instead of raw ANSI the UI would have to interpret.
|
|
176
|
+
*
|
|
177
|
+
* Trailing blank lines are dropped: a TUI pads the screen to its full height, and
|
|
178
|
+
* keeping that padding would make every snapshot differ on cursor movement alone.
|
|
179
|
+
*/
|
|
180
|
+
export async function capturePane(target) {
|
|
181
|
+
const args = ["capture-pane", "-p"];
|
|
182
|
+
if (target)
|
|
183
|
+
args.push("-t", target);
|
|
184
|
+
const { stdout } = await run("tmux", args, { maxBuffer: 4 * 1024 * 1024 });
|
|
185
|
+
return normalizeSnapshot(stdout);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Trim a raw capture into what gets stored: trailing blank lines dropped, and
|
|
189
|
+
* the TAIL kept when oversized (the bottom of a screen is where current output
|
|
190
|
+
* is; truncating the head loses scrolled-away context, truncating the tail would
|
|
191
|
+
* lose the live part).
|
|
192
|
+
*/
|
|
193
|
+
export function normalizeSnapshot(raw, max = MAX_SNAPSHOT_CHARS) {
|
|
194
|
+
const trimmed = raw.replace(/\s+$/u, "");
|
|
195
|
+
return trimmed.length > max ? trimmed.slice(trimmed.length - max) : trimmed;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Deliver a notice to a pane. The two modes are deliberately different
|
|
199
|
+
* MECHANISMS, not the same one with a flag:
|
|
200
|
+
*
|
|
201
|
+
* - `submit: false` → `display-message`, which paints the status line and never
|
|
202
|
+
* touches the input buffer. Typing an unsubmitted notice instead (the obvious
|
|
203
|
+
* first attempt) corrupts whatever the human or agent was mid-way through
|
|
204
|
+
* typing, and successive notices concatenate into one unreadable line.
|
|
205
|
+
* - `submit: true` → `send-keys -l` + `Enter`, delivering the notice as input so
|
|
206
|
+
* the agent actually processes it. `-l` sends the text LITERALLY; without it
|
|
207
|
+
* tmux reads words like "Enter" as key names.
|
|
208
|
+
*
|
|
209
|
+
* Non-submit is for a human watching the pane; submit is for waking the agent.
|
|
210
|
+
*/
|
|
211
|
+
/**
|
|
212
|
+
* Pause between typing a submitted notice and pressing Enter.
|
|
213
|
+
*
|
|
214
|
+
* #108: Codex's composer treats a CR that arrives in the same input burst as
|
|
215
|
+
* the preceding text as a PASTED newline, not a submit — so back-to-back
|
|
216
|
+
* `send-keys -l` + `send-keys Enter` left the notice sitting in the composer.
|
|
217
|
+
* Measured live against Codex 0.139: the immediate Enter is swallowed; the
|
|
218
|
+
* same Enter ≥50ms later submits every time. 150ms buys a 3× margin and is
|
|
219
|
+
* imperceptible next to the 1s mirror cadence. Claude Code tolerates the
|
|
220
|
+
* pause too, so it is unconditional rather than per-TUI.
|
|
221
|
+
*/
|
|
222
|
+
export const SUBMIT_ENTER_DELAY_MS = 150;
|
|
223
|
+
/**
|
|
224
|
+
* #108 round two: for a LARGE notice (~1KB — a full message with attachment
|
|
225
|
+
* manifest), Codex collapses the burst into a "[Pasted Content N chars]" chip
|
|
226
|
+
* and CONSUMES the first Enter outright — no delay fixes it (measured on
|
|
227
|
+
* Codex 0.139: a single Enter still failed 2s after the text). What does work
|
|
228
|
+
* is pressing Enter AGAIN once the paste has settled. So after the first
|
|
229
|
+
* Enter, press it twice more on this schedule. The retries are no-ops when
|
|
230
|
+
* the first press already submitted: an empty composer ignores Enter, and a
|
|
231
|
+
* running turn does too (verified live with small and large payloads on
|
|
232
|
+
* Codex 0.139 and 0.146, and Claude Code behaves the same). Cost: ~2.7s per
|
|
233
|
+
* delivery inside the serial pane queue — chat cadence, not a hot path.
|
|
234
|
+
*/
|
|
235
|
+
export const SUBMIT_ENTER_RETRIES_MS = [1000, 1500];
|
|
236
|
+
export async function notifyPane(text, target, submit = false, deps = {}) {
|
|
237
|
+
const exec = deps.exec ?? ((args) => run("tmux", args));
|
|
238
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
239
|
+
const base = target ? ["-t", target] : [];
|
|
240
|
+
// The two modes have different limits because they are different surfaces.
|
|
241
|
+
// `display-message` paints tmux's status line — one row, so a long notice is
|
|
242
|
+
// unreadable there. `send-keys` TYPES into the pane, where the agent reads it
|
|
243
|
+
// as input, and cutting at 240 silently lost the tail of real messages: a
|
|
244
|
+
// request arrived half-written and was answered as if complete.
|
|
245
|
+
const line = sanitizeForPane(text, submit ? MAX_TYPED_NOTICE_CHARS : 240);
|
|
246
|
+
if (!submit) {
|
|
247
|
+
await exec(["display-message", ...base, line]);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
await exec(["send-keys", ...base, "-l", line]);
|
|
251
|
+
await sleep(SUBMIT_ENTER_DELAY_MS);
|
|
252
|
+
await exec(["send-keys", ...base, "Enter"]);
|
|
253
|
+
// Re-press Enter after the paste settles — see SUBMIT_ENTER_RETRIES_MS.
|
|
254
|
+
for (const delay of SUBMIT_ENTER_RETRIES_MS) {
|
|
255
|
+
await sleep(delay);
|
|
256
|
+
await exec(["send-keys", ...base, "Enter"]);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Send ONE key to a pane, by tmux key NAME (not literally).
|
|
261
|
+
*
|
|
262
|
+
* Answering a prompt means pressing keys, not typing text: "Right" must arrive
|
|
263
|
+
* as the arrow key that opens the review pane, and "1" as the digit that toggles
|
|
264
|
+
* an option. `send-keys -l` (used for message delivery) would type the literal
|
|
265
|
+
* word "Right", so this deliberately omits it.
|
|
266
|
+
*/
|
|
267
|
+
export async function sendKeyToPane(key, target) {
|
|
268
|
+
const base = target ? ["-t", target] : [];
|
|
269
|
+
// Whitelist: only ever send a bare digit or a named navigation key. Without
|
|
270
|
+
// this, an answer travelling from the server could name any tmux key —
|
|
271
|
+
// "C-c" would kill the agent.
|
|
272
|
+
if (!/^(?:[0-9]|Right|Left|Up|Down|Enter|Space|Tab|Escape|BSpace)$/.test(key)) {
|
|
273
|
+
throw new Error(`refusing to send unrecognised key "${key}"`);
|
|
274
|
+
}
|
|
275
|
+
await run("tmux", ["send-keys", ...base, key]);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* How long to wait between keystrokes in a replayed answer.
|
|
279
|
+
*
|
|
280
|
+
* Keys fired back to back sometimes dropped the middle ones: Claude Code's
|
|
281
|
+
* multi-select re-renders on each toggle, and a keystroke arriving mid-render
|
|
282
|
+
* was lost. Observed non-deterministically — [1,2,3,4] recorded once as {1,4}
|
|
283
|
+
* and once as {1,2,3,4} from identical input. A short pause lets the TUI settle
|
|
284
|
+
* between keys. Human-in-the-loop, so ~700ms for a four-option answer is fine.
|
|
285
|
+
*/
|
|
286
|
+
const KEY_SEQUENCE_DELAY_MS = 90;
|
|
287
|
+
/**
|
|
288
|
+
* Send a sequence of keys to a pane with a pause BETWEEN each (never after the
|
|
289
|
+
* last), so a re-rendering TUI does not drop the ones in the middle.
|
|
290
|
+
*/
|
|
291
|
+
export async function sendKeySequence(keys, target, deps = {}) {
|
|
292
|
+
const sendKey = deps.sendKey ?? sendKeyToPane;
|
|
293
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
294
|
+
const delayMs = deps.delayMs ?? KEY_SEQUENCE_DELAY_MS;
|
|
295
|
+
for (let i = 0; i < keys.length; i++) {
|
|
296
|
+
if (i > 0)
|
|
297
|
+
await sleep(delayMs);
|
|
298
|
+
await sendKey(keys[i], target);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/** Type literal text into a pane (message-delivery style), newlines stripped. */
|
|
302
|
+
export async function typeTextToPane(text, target) {
|
|
303
|
+
const base = target ? ["-t", target] : [];
|
|
304
|
+
const clean = sanitizeForPane(text, 2000);
|
|
305
|
+
if (clean.length)
|
|
306
|
+
await run("tmux", ["send-keys", ...base, "-l", clean]);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* How long to let a text field settle around a `{text}` step.
|
|
310
|
+
*
|
|
311
|
+
* A text input MOUNTS and FOCUSES when the caret navigates onto it — unlike a
|
|
312
|
+
* checkbox, which toggles instantly. A live test showed the plain 90ms inter-key
|
|
313
|
+
* gap fired the typed text before the field was focused, and the whole value was
|
|
314
|
+
* dropped. So a text step waits longer BEFORE (let the field focus) and AFTER
|
|
315
|
+
* (let the value register before the next key) than an ordinary key press.
|
|
316
|
+
*/
|
|
317
|
+
const TEXT_SETTLE_MS = 400;
|
|
318
|
+
/** Send a mixed sequence of key presses and literal-text injections, paced. */
|
|
319
|
+
export async function sendKeySteps(steps, target, deps = {}) {
|
|
320
|
+
const sendKey = deps.sendKey ?? sendKeyToPane;
|
|
321
|
+
const typeText = deps.typeText ?? typeTextToPane;
|
|
322
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
323
|
+
const delayMs = deps.delayMs ?? KEY_SEQUENCE_DELAY_MS;
|
|
324
|
+
for (let i = 0; i < steps.length; i++) {
|
|
325
|
+
const step = steps[i];
|
|
326
|
+
const prev = steps[i - 1];
|
|
327
|
+
// A text step needs the field focused first; the step AFTER a text step
|
|
328
|
+
// needs the typed value to have registered. Both gaps are longer than the
|
|
329
|
+
// ordinary inter-key pause.
|
|
330
|
+
if (i > 0) {
|
|
331
|
+
const brackets = "text" in step || (prev && "text" in prev);
|
|
332
|
+
await sleep(brackets ? TEXT_SETTLE_MS : delayMs);
|
|
333
|
+
}
|
|
334
|
+
if ("text" in step)
|
|
335
|
+
await typeText(step.text, target);
|
|
336
|
+
else
|
|
337
|
+
await sendKey(step.key, target);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/** Describe a pane for the session label, e.g. "0:0.0 (claude)". */
|
|
341
|
+
export async function describePane(target) {
|
|
342
|
+
const args = ["display-message", "-p"];
|
|
343
|
+
if (target)
|
|
344
|
+
args.push("-t", target);
|
|
345
|
+
args.push("#{session_name}:#{window_index}.#{pane_index} (#{pane_current_command})");
|
|
346
|
+
try {
|
|
347
|
+
const { stdout } = await run("tmux", args);
|
|
348
|
+
return stdout.trim();
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
return target ?? "tmux";
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Stream a pane into an EXISTING session until stopped. Returns a stop function.
|
|
356
|
+
*
|
|
357
|
+
* Split out of the CLI path so the MCP server can run the same loop in-process:
|
|
358
|
+
* the mirror should not be a second thing a human remembers to launch.
|
|
359
|
+
*/
|
|
360
|
+
export function startMirrorLoop(options) {
|
|
361
|
+
const { client, session, target } = options;
|
|
362
|
+
const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
363
|
+
const log = options.log ?? (() => { });
|
|
364
|
+
let lastSent = null;
|
|
365
|
+
let lastNotifiedMessageId = 0;
|
|
366
|
+
let stopped = false;
|
|
367
|
+
const notifyIfUnread = async () => {
|
|
368
|
+
const channels = await client.listChannels();
|
|
369
|
+
let unread = 0;
|
|
370
|
+
let channelsWithUnread = 0;
|
|
371
|
+
let highest = 0;
|
|
372
|
+
for (const channel of channels) {
|
|
373
|
+
const page = await client.listMessages({ channel: channel.id, unread: true });
|
|
374
|
+
const rows = page.messages;
|
|
375
|
+
if (!rows.length)
|
|
376
|
+
continue;
|
|
377
|
+
unread += rows.length;
|
|
378
|
+
channelsWithUnread += 1;
|
|
379
|
+
for (const row of rows)
|
|
380
|
+
if (typeof row.id === "number" && row.id > highest)
|
|
381
|
+
highest = row.id;
|
|
382
|
+
}
|
|
383
|
+
if (!unread || highest <= lastNotifiedMessageId)
|
|
384
|
+
return;
|
|
385
|
+
lastNotifiedMessageId = highest;
|
|
386
|
+
await notifyPane(buildNotice(unread, channelsWithUnread), target, options.notifySubmit);
|
|
387
|
+
log(` notified: ${unread} unread in ${channelsWithUnread} channel(s)`);
|
|
388
|
+
};
|
|
389
|
+
let lastSentAt = 0;
|
|
390
|
+
let lastContactAt = Date.now();
|
|
391
|
+
let inFlight = false;
|
|
392
|
+
const tick = async () => {
|
|
393
|
+
if (stopped || inFlight)
|
|
394
|
+
return;
|
|
395
|
+
try {
|
|
396
|
+
const current = await capturePane(target);
|
|
397
|
+
const now = Date.now();
|
|
398
|
+
const changed = current !== lastSent;
|
|
399
|
+
if (changed && now - lastSentAt >= MIN_SEND_INTERVAL_MS) {
|
|
400
|
+
inFlight = true;
|
|
401
|
+
try {
|
|
402
|
+
await client.appendFrame(session, { stream: "snapshot", content: current });
|
|
403
|
+
}
|
|
404
|
+
finally {
|
|
405
|
+
inFlight = false;
|
|
406
|
+
}
|
|
407
|
+
// Only mark it sent AFTER the write lands: a failed send must stay
|
|
408
|
+
// pending so the next tick retries rather than silently dropping it.
|
|
409
|
+
lastSent = current;
|
|
410
|
+
lastSentAt = now;
|
|
411
|
+
lastContactAt = now;
|
|
412
|
+
}
|
|
413
|
+
else if (!changed && now - lastContactAt >= HEARTBEAT_INTERVAL_MS) {
|
|
414
|
+
// Quiet screen: the only reason to touch the network is liveness.
|
|
415
|
+
await client.heartbeat(session, "busy");
|
|
416
|
+
lastContactAt = now;
|
|
417
|
+
}
|
|
418
|
+
// A change seen too soon after the last send is NOT dropped — `lastSent`
|
|
419
|
+
// is unchanged, so the next eligible tick sends the newest capture. That
|
|
420
|
+
// trailing send is what stops the view freezing on a stale screen when
|
|
421
|
+
// activity stops abruptly.
|
|
422
|
+
if (options.notify)
|
|
423
|
+
await notifyIfUnread();
|
|
424
|
+
}
|
|
425
|
+
catch (err) {
|
|
426
|
+
// Transient backend/tmux errors must not kill a long-running mirror.
|
|
427
|
+
inFlight = false;
|
|
428
|
+
log(` warn: ${err.message.split("\n")[0]}`);
|
|
429
|
+
options.onError?.(err);
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
const timer = setInterval(() => void tick(), intervalMs);
|
|
433
|
+
// Don't hold the process open on our account — the MCP transport decides
|
|
434
|
+
// lifetime, not this poller.
|
|
435
|
+
timer.unref?.();
|
|
436
|
+
void tick();
|
|
437
|
+
return () => {
|
|
438
|
+
stopped = true;
|
|
439
|
+
clearInterval(timer);
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Auto-start mirroring for an agent that is running inside tmux. Silently does
|
|
444
|
+
* nothing when there is no pane to find — being outside tmux is normal.
|
|
445
|
+
*/
|
|
446
|
+
export async function startAutoMirror(options) {
|
|
447
|
+
const pane = await detectTmuxPane();
|
|
448
|
+
if (!pane)
|
|
449
|
+
return null;
|
|
450
|
+
const log = options.log ?? (() => { });
|
|
451
|
+
log(`mirroring tmux pane ${pane} into the dashboard terminal`);
|
|
452
|
+
return startMirrorLoop({ ...options, target: pane });
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Mirror the pane until interrupted. Resolves with an exit code.
|
|
456
|
+
*/
|
|
457
|
+
export async function runTmuxMirror(options = {}) {
|
|
458
|
+
const log = options.log ?? ((l) => process.stdout.write(`${l}\n`));
|
|
459
|
+
const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
460
|
+
if (!(await tmuxAvailable())) {
|
|
461
|
+
log("FAIL tmux is not available (is a tmux server running?)");
|
|
462
|
+
return 1;
|
|
463
|
+
}
|
|
464
|
+
let resolved;
|
|
465
|
+
try {
|
|
466
|
+
resolved = options.resolved ?? loadProfile({ profile: options.profile });
|
|
467
|
+
}
|
|
468
|
+
catch (err) {
|
|
469
|
+
log(`FAIL ${err.message}`);
|
|
470
|
+
return 1;
|
|
471
|
+
}
|
|
472
|
+
// Fail fast on an unreachable pane rather than looping on errors.
|
|
473
|
+
let screen;
|
|
474
|
+
try {
|
|
475
|
+
screen = await capturePane(options.target);
|
|
476
|
+
}
|
|
477
|
+
catch (err) {
|
|
478
|
+
log(`FAIL could not capture pane${options.target ? ` "${options.target}"` : ""}.`);
|
|
479
|
+
log(` ${err.message.split("\n")[0]}`);
|
|
480
|
+
log(" List panes with: tmux list-panes -a");
|
|
481
|
+
return 1;
|
|
482
|
+
}
|
|
483
|
+
const label = await describePane(options.target);
|
|
484
|
+
const client = new TaskflowClient({ server: resolved.server, key: resolved.key });
|
|
485
|
+
let session;
|
|
486
|
+
try {
|
|
487
|
+
const row = await client.registerSession({
|
|
488
|
+
// Stable per pane, so re-running reconnects the same session instead of
|
|
489
|
+
// piling up rows the human has to pick between.
|
|
490
|
+
session_identifier: `tmux:${hostname()}:${label.split(" ")[0]}`,
|
|
491
|
+
host: hostname(),
|
|
492
|
+
pid: process.pid,
|
|
493
|
+
cwd: process.cwd(),
|
|
494
|
+
transport: "tmux",
|
|
495
|
+
});
|
|
496
|
+
session = row.id;
|
|
497
|
+
}
|
|
498
|
+
catch (err) {
|
|
499
|
+
log(`FAIL could not register a session: ${err.message}`);
|
|
500
|
+
return 1;
|
|
501
|
+
}
|
|
502
|
+
log(`Mirroring ${label} → ${resolved.server} as "${resolved.displayName}" (session ${session}).`);
|
|
503
|
+
log(`Sending a snapshot whenever the screen changes, every ${intervalMs}ms. Ctrl-C to stop.`);
|
|
504
|
+
if (options.notify) {
|
|
505
|
+
log(options.notifySubmit
|
|
506
|
+
? "Notify: ON (submit) — unread notices are typed into the pane AND submitted, so the agent acts on them unprompted."
|
|
507
|
+
: "Notify: ON — unread notices show on the tmux status line, leaving the input untouched. Use --notify-submit to hand them to the agent instead.");
|
|
508
|
+
}
|
|
509
|
+
let lastSent = null;
|
|
510
|
+
let sent = 0;
|
|
511
|
+
let stopping = false;
|
|
512
|
+
const stop = async (signal) => {
|
|
513
|
+
if (stopping)
|
|
514
|
+
return;
|
|
515
|
+
stopping = true;
|
|
516
|
+
log(`\n${signal} — closing session ${session} (${sent} snapshot${sent === 1 ? "" : "s"} sent).`);
|
|
517
|
+
try {
|
|
518
|
+
await client.closeSession(session);
|
|
519
|
+
}
|
|
520
|
+
catch {
|
|
521
|
+
/* best effort: the session goes stale on its own within the heartbeat window */
|
|
522
|
+
}
|
|
523
|
+
process.exit(0);
|
|
524
|
+
};
|
|
525
|
+
process.on("SIGINT", () => void stop("SIGINT"));
|
|
526
|
+
process.on("SIGTERM", () => void stop("SIGTERM"));
|
|
527
|
+
// Notices are rate-limited to one per unread BURST: re-typing the same notice
|
|
528
|
+
// every tick would bury the pane and, with submit on, re-prompt the agent in a
|
|
529
|
+
// loop. A new notice only goes out when the highest unread id has moved, i.e.
|
|
530
|
+
// when something genuinely new arrived.
|
|
531
|
+
let lastNotifiedMessageId = 0;
|
|
532
|
+
/** Poll unread across channels; type one notice if there is anything new. */
|
|
533
|
+
const notifyIfUnread = async () => {
|
|
534
|
+
const channels = await client.listChannels();
|
|
535
|
+
let unread = 0;
|
|
536
|
+
let channelsWithUnread = 0;
|
|
537
|
+
let highest = 0;
|
|
538
|
+
for (const channel of channels) {
|
|
539
|
+
const page = await client.listMessages({ channel: channel.id, unread: true });
|
|
540
|
+
const rows = page.messages;
|
|
541
|
+
if (!rows.length)
|
|
542
|
+
continue;
|
|
543
|
+
unread += rows.length;
|
|
544
|
+
channelsWithUnread += 1;
|
|
545
|
+
for (const row of rows)
|
|
546
|
+
if (typeof row.id === "number" && row.id > highest)
|
|
547
|
+
highest = row.id;
|
|
548
|
+
}
|
|
549
|
+
if (!unread || highest <= lastNotifiedMessageId)
|
|
550
|
+
return;
|
|
551
|
+
lastNotifiedMessageId = highest;
|
|
552
|
+
await notifyPane(buildNotice(unread, channelsWithUnread), options.target, options.notifySubmit);
|
|
553
|
+
log(` notified: ${unread} unread in ${channelsWithUnread} channel(s)`);
|
|
554
|
+
};
|
|
555
|
+
// Heartbeat every tick so the agent reads as online for as long as we mirror;
|
|
556
|
+
// liveness is heartbeat-recency based, so a silent screen must still beat.
|
|
557
|
+
for (;;) {
|
|
558
|
+
if (stopping)
|
|
559
|
+
return 0;
|
|
560
|
+
try {
|
|
561
|
+
const current = await capturePane(options.target);
|
|
562
|
+
if (current !== lastSent) {
|
|
563
|
+
await client.appendFrame(session, { stream: "snapshot", content: current });
|
|
564
|
+
lastSent = current;
|
|
565
|
+
sent += 1;
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
await client.heartbeat(session, "busy");
|
|
569
|
+
}
|
|
570
|
+
if (options.notify)
|
|
571
|
+
await notifyIfUnread();
|
|
572
|
+
}
|
|
573
|
+
catch (err) {
|
|
574
|
+
// Transient backend/tmux errors must not kill a long-running mirror.
|
|
575
|
+
log(` warn: ${err.message.split("\n")[0]}`);
|
|
576
|
+
}
|
|
577
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
//# sourceMappingURL=tmux.js.map
|
package/dist/tmux.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tmux.js","sourceRoot":"","sources":["../src/tmux.ts"],"names":[],"mappings":"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"}
|