@getpaseo/server 0.1.33 → 0.1.35

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.
Files changed (111) hide show
  1. package/dist/server/server/agent/activity-curator.d.ts.map +1 -1
  2. package/dist/server/server/agent/activity-curator.js +15 -1
  3. package/dist/server/server/agent/activity-curator.js.map +1 -1
  4. package/dist/server/server/agent/agent-management-mcp.d.ts.map +1 -1
  5. package/dist/server/server/agent/agent-management-mcp.js +2 -4
  6. package/dist/server/server/agent/agent-management-mcp.js.map +1 -1
  7. package/dist/server/server/agent/agent-manager.d.ts +25 -14
  8. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  9. package/dist/server/server/agent/agent-manager.js +383 -337
  10. package/dist/server/server/agent/agent-manager.js.map +1 -1
  11. package/dist/server/server/agent/agent-sdk-types.d.ts +16 -14
  12. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  13. package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
  14. package/dist/server/server/agent/mcp-server.js +2 -4
  15. package/dist/server/server/agent/mcp-server.js.map +1 -1
  16. package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
  17. package/dist/server/server/agent/provider-launch-config.js +15 -1
  18. package/dist/server/server/agent/provider-launch-config.js.map +1 -1
  19. package/dist/server/server/agent/provider-manifest.d.ts +1 -1
  20. package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
  21. package/dist/server/server/agent/provider-manifest.js +4 -4
  22. package/dist/server/server/agent/provider-manifest.js.map +1 -1
  23. package/dist/server/server/agent/providers/claude/partial-json.js +3 -3
  24. package/dist/server/server/agent/providers/claude/partial-json.js.map +1 -1
  25. package/dist/server/server/agent/providers/claude/sdk-model-resolver.d.ts +11 -0
  26. package/dist/server/server/agent/providers/claude/sdk-model-resolver.d.ts.map +1 -0
  27. package/dist/server/server/agent/providers/claude/sdk-model-resolver.js +104 -0
  28. package/dist/server/server/agent/providers/claude/sdk-model-resolver.js.map +1 -0
  29. package/dist/server/server/agent/providers/claude-agent.d.ts +4 -13
  30. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  31. package/dist/server/server/agent/providers/claude-agent.js +382 -453
  32. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  33. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
  34. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +42 -7
  35. package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
  36. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +5 -3
  37. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  38. package/dist/server/server/agent/providers/codex-app-server-agent.js +174 -151
  39. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  40. package/dist/server/server/agent/providers/opencode-agent.d.ts +3 -3
  41. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  42. package/dist/server/server/agent/providers/opencode-agent.js +212 -25
  43. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  44. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts +3 -0
  45. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts.map +1 -0
  46. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js +57 -0
  47. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js.map +1 -0
  48. package/dist/server/server/config.d.ts.map +1 -1
  49. package/dist/server/server/config.js +1 -5
  50. package/dist/server/server/config.js.map +1 -1
  51. package/dist/server/server/session.d.ts.map +1 -1
  52. package/dist/server/server/session.js +14 -12
  53. package/dist/server/server/session.js.map +1 -1
  54. package/dist/server/shared/tool-call-display.js +1 -1
  55. package/dist/server/shared/tool-call-display.js.map +1 -1
  56. package/dist/src/server/agent/activity-curator.js +15 -1
  57. package/dist/src/server/agent/activity-curator.js.map +1 -1
  58. package/dist/src/server/agent/agent-manager.js +383 -337
  59. package/dist/src/server/agent/agent-manager.js.map +1 -1
  60. package/dist/src/server/agent/mcp-server.js +2 -4
  61. package/dist/src/server/agent/mcp-server.js.map +1 -1
  62. package/dist/src/server/agent/provider-launch-config.js +15 -1
  63. package/dist/src/server/agent/provider-launch-config.js.map +1 -1
  64. package/dist/src/server/agent/provider-manifest.js +4 -4
  65. package/dist/src/server/agent/provider-manifest.js.map +1 -1
  66. package/dist/src/server/agent/providers/claude/partial-json.js +3 -3
  67. package/dist/src/server/agent/providers/claude/partial-json.js.map +1 -1
  68. package/dist/src/server/agent/providers/claude/sdk-model-resolver.js +104 -0
  69. package/dist/src/server/agent/providers/claude/sdk-model-resolver.js.map +1 -0
  70. package/dist/src/server/agent/providers/claude-agent.js +382 -453
  71. package/dist/src/server/agent/providers/claude-agent.js.map +1 -1
  72. package/dist/src/server/agent/providers/codex/tool-call-mapper.js +42 -7
  73. package/dist/src/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
  74. package/dist/src/server/agent/providers/codex-app-server-agent.js +174 -151
  75. package/dist/src/server/agent/providers/codex-app-server-agent.js.map +1 -1
  76. package/dist/src/server/agent/providers/opencode-agent.js +212 -25
  77. package/dist/src/server/agent/providers/opencode-agent.js.map +1 -1
  78. package/dist/src/server/config.js +1 -5
  79. package/dist/src/server/config.js.map +1 -1
  80. package/dist/src/server/session.js +14 -12
  81. package/dist/src/server/session.js.map +1 -1
  82. package/dist/src/shared/tool-call-display.js +1 -1
  83. package/dist/src/shared/tool-call-display.js.map +1 -1
  84. package/package.json +4 -5
  85. package/agent-prompt.md +0 -339
  86. package/dist/server/server/agent/providers/claude/model-catalog.d.ts +0 -29
  87. package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +0 -1
  88. package/dist/server/server/agent/providers/claude/model-catalog.js +0 -70
  89. package/dist/server/server/agent/providers/claude/model-catalog.js.map +0 -1
  90. package/dist/server/server/agent/system-prompt.d.ts +0 -3
  91. package/dist/server/server/agent/system-prompt.d.ts.map +0 -1
  92. package/dist/server/server/agent/system-prompt.js +0 -19
  93. package/dist/server/server/agent/system-prompt.js.map +0 -1
  94. package/dist/server/server/terminal-mcp/index.d.ts +0 -4
  95. package/dist/server/server/terminal-mcp/index.d.ts.map +0 -1
  96. package/dist/server/server/terminal-mcp/index.js +0 -3
  97. package/dist/server/server/terminal-mcp/index.js.map +0 -1
  98. package/dist/server/server/terminal-mcp/server.d.ts +0 -10
  99. package/dist/server/server/terminal-mcp/server.d.ts.map +0 -1
  100. package/dist/server/server/terminal-mcp/server.js +0 -209
  101. package/dist/server/server/terminal-mcp/server.js.map +0 -1
  102. package/dist/server/server/terminal-mcp/terminal-manager.d.ts +0 -123
  103. package/dist/server/server/terminal-mcp/terminal-manager.d.ts.map +0 -1
  104. package/dist/server/server/terminal-mcp/terminal-manager.js +0 -339
  105. package/dist/server/server/terminal-mcp/terminal-manager.js.map +0 -1
  106. package/dist/server/server/terminal-mcp/tmux.d.ts +0 -207
  107. package/dist/server/server/terminal-mcp/tmux.d.ts.map +0 -1
  108. package/dist/server/server/terminal-mcp/tmux.js +0 -821
  109. package/dist/server/server/terminal-mcp/tmux.js.map +0 -1
  110. package/dist/src/server/agent/providers/claude/model-catalog.js +0 -70
  111. package/dist/src/server/agent/providers/claude/model-catalog.js.map +0 -1
@@ -1,821 +0,0 @@
1
- import { exec as execCallback, execFile as execFileCallback } from "child_process";
2
- import { promisify } from "util";
3
- import { v4 as uuidv4 } from "uuid";
4
- import os from "node:os";
5
- const exec = promisify(execCallback);
6
- const execFile = promisify(execFileCallback);
7
- let shellConfig = { type: "bash" };
8
- const ANSI_ESCAPE_REGEX = /\u001B[\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
9
- const EXIT_CODE_MARKER = "__PASEO_EXIT_CODE__:";
10
- function stripAnsiSequences(value) {
11
- if (!value) {
12
- return "";
13
- }
14
- return value.replace(ANSI_ESCAPE_REGEX, "");
15
- }
16
- export function extractExitCodeMarkerFromOutput(output) {
17
- if (!output) {
18
- return { exitCode: null, output };
19
- }
20
- let exitCode = null;
21
- const kept = [];
22
- for (const line of output.split("\n")) {
23
- const trimmed = line.trim();
24
- if (trimmed.startsWith(EXIT_CODE_MARKER)) {
25
- const codeStr = trimmed.slice(EXIT_CODE_MARKER.length).trim();
26
- const parsed = parseInt(codeStr, 10);
27
- if (Number.isFinite(parsed)) {
28
- exitCode = parsed;
29
- }
30
- continue;
31
- }
32
- kept.push(line);
33
- }
34
- return { exitCode, output: kept.join("\n").trimEnd() };
35
- }
36
- export function setShellConfig(config) {
37
- // Validate shell type
38
- const validShells = ["bash", "zsh", "fish"];
39
- if (validShells.includes(config.type)) {
40
- shellConfig = { type: config.type };
41
- }
42
- else {
43
- shellConfig = { type: "bash" };
44
- }
45
- }
46
- /**
47
- * Execute a tmux command and return the result
48
- * Uses execFile to avoid shell interpretation of special characters
49
- */
50
- export async function executeTmux(args) {
51
- try {
52
- const { stdout } = await execFile("tmux", args);
53
- return stdout.trim();
54
- }
55
- catch (error) {
56
- throw new Error(`Failed to execute tmux command: ${error.message}`);
57
- }
58
- }
59
- /**
60
- * Check if tmux server is running
61
- */
62
- export async function isTmuxRunning() {
63
- try {
64
- await executeTmux(["list-sessions", "-F", "#{session_name}"]);
65
- return true;
66
- }
67
- catch (error) {
68
- return false;
69
- }
70
- }
71
- /**
72
- * List all tmux sessions
73
- */
74
- export async function listSessions() {
75
- const format = "#{session_id}:#{session_name}:#{?session_attached,1,0}:#{session_windows}";
76
- const output = await executeTmux(["list-sessions", "-F", format]);
77
- if (!output)
78
- return [];
79
- return output.split("\n").map((line) => {
80
- const [id, name, attached, windows] = line.split(":");
81
- return {
82
- id,
83
- name,
84
- attached: attached === "1",
85
- windows: parseInt(windows, 10),
86
- };
87
- });
88
- }
89
- /**
90
- * Find a session by name
91
- */
92
- export async function findSessionByName(name) {
93
- try {
94
- const sessions = await listSessions();
95
- return sessions.find((session) => session.name === name) || null;
96
- }
97
- catch (error) {
98
- return null;
99
- }
100
- }
101
- /**
102
- * Find a window by name in a session
103
- */
104
- export async function findWindowByName(sessionId, name) {
105
- try {
106
- const windows = await listWindows(sessionId);
107
- return windows.find((window) => window.name === name) || null;
108
- }
109
- catch (error) {
110
- return null;
111
- }
112
- }
113
- /**
114
- * Check if a window name is unique in a session
115
- */
116
- export async function isWindowNameUnique(sessionId, name) {
117
- const window = await findWindowByName(sessionId, name);
118
- return window === null;
119
- }
120
- /**
121
- * List windows in a session
122
- */
123
- export async function listWindows(sessionId) {
124
- const format = "#{window_id}:#{window_name}:#{?window_active,1,0}";
125
- const output = await executeTmux(["list-windows", "-t", sessionId, "-F", format]);
126
- if (!output)
127
- return [];
128
- return output.split("\n").map((line) => {
129
- const [id, name, active] = line.split(":");
130
- return {
131
- id,
132
- name,
133
- active: active === "1",
134
- sessionId,
135
- };
136
- });
137
- }
138
- /**
139
- * List panes in a window
140
- */
141
- export async function listPanes(windowId) {
142
- const format = "#{pane_id}:#{pane_title}:#{?pane_active,1,0}";
143
- const output = await executeTmux(["list-panes", "-t", windowId, "-F", format]);
144
- if (!output)
145
- return [];
146
- return output.split("\n").map((line) => {
147
- const [id, title, active] = line.split(":");
148
- return {
149
- id,
150
- windowId,
151
- title: title,
152
- active: active === "1",
153
- };
154
- });
155
- }
156
- /**
157
- * Capture content from a specific pane, by default the latest 200 lines.
158
- */
159
- export async function capturePaneContent(paneId, lines = 200, includeColors = false) {
160
- // Capture a large range to ensure we have enough content
161
- const captureLines = Math.max(lines, 1000);
162
- const args = ["capture-pane", "-p"];
163
- if (includeColors) {
164
- args.push("-e");
165
- }
166
- args.push("-t", paneId, "-S", `-${captureLines}`, "-E", "-");
167
- const output = await executeTmux(args);
168
- // Trim trailing whitespace, split by lines, take last N lines, rejoin
169
- const trimmed = output.trimEnd();
170
- const allLines = trimmed.split("\n");
171
- const lastLines = allLines.slice(-lines);
172
- const joined = lastLines.join("\n");
173
- return includeColors ? joined : stripAnsiSequences(joined);
174
- }
175
- /**
176
- * Get the current working directory of a pane
177
- */
178
- export async function getCurrentWorkingDirectory(paneId) {
179
- try {
180
- const tmuxPath = await executeTmux([
181
- "display-message",
182
- "-p",
183
- "-t",
184
- paneId,
185
- "#{pane_current_path}",
186
- ]);
187
- // If tmux returns a valid path, use it
188
- if (tmuxPath && tmuxPath.trim()) {
189
- return tmuxPath;
190
- }
191
- // Fallback: get the PID and use lsof to find the actual CWD
192
- const shellPid = await executeTmux(["display-message", "-p", "-t", paneId, "#{pane_pid}"]);
193
- const { stdout } = await exec(`lsof -a -p ${shellPid.trim()} -d cwd -Fn | grep '^n' | cut -c2-`);
194
- return stdout.trim() || tmuxPath;
195
- }
196
- catch (error) {
197
- // If all else fails, return empty string
198
- return "";
199
- }
200
- }
201
- /**
202
- * Get stored working directory from window user option (for dead panes)
203
- * Falls back to current working directory if not stored
204
- */
205
- export async function getStoredWorkingDirectory(windowId, paneId) {
206
- try {
207
- const stored = await executeTmux([
208
- "show-window-options",
209
- "-t",
210
- windowId,
211
- "-v",
212
- "@working_directory",
213
- ]);
214
- if (stored && stored.trim()) {
215
- return stored.trim();
216
- }
217
- }
218
- catch (error) {
219
- // Option not set, fall back to current
220
- }
221
- return getCurrentWorkingDirectory(paneId);
222
- }
223
- /**
224
- * Get the current command running in a pane (full command line with arguments)
225
- * Gets the immediate child process of the shell, not the shell itself
226
- */
227
- export async function getCurrentCommand(paneId) {
228
- try {
229
- // Get the shell PID (the pane's main process)
230
- const shellPid = await executeTmux(["display-message", "-p", "-t", paneId, "#{pane_pid}"]);
231
- // First, check if there's a child process using comm= (works for all programs including top)
232
- // Use 'ax' flags to see all processes
233
- const { stdout: childPid } = await exec(`ps ax -o pid=,ppid=,comm= | awk '$2 == ${shellPid.trim()} { print $1; exit }'`);
234
- if (childPid.trim()) {
235
- // Found a child process, get its full command with args
236
- const { stdout: fullCmd } = await exec(`ps -p ${childPid.trim()} -o args= | sed 's/\\\\012.*//'`);
237
- const command = fullCmd.trim();
238
- if (command) {
239
- return command;
240
- }
241
- }
242
- // No child process, just return the shell name
243
- const { stdout: shellCmd } = await exec(`ps -p ${shellPid} -o comm=`);
244
- return shellCmd.trim();
245
- }
246
- catch (error) {
247
- // Fallback to just the command name if ps fails
248
- return executeTmux(["display-message", "-p", "-t", paneId, "#{pane_current_command}"]);
249
- }
250
- }
251
- /**
252
- * Get stored command from window user option (for dead panes)
253
- * Falls back to current command if not stored
254
- */
255
- export async function getStoredCommand(windowId, paneId) {
256
- try {
257
- const stored = await executeTmux(["show-window-options", "-t", windowId, "-v", "@command"]);
258
- if (stored && stored.trim()) {
259
- return stored.trim();
260
- }
261
- }
262
- catch (error) {
263
- // Option not set, fall back to current
264
- }
265
- return getCurrentCommand(paneId);
266
- }
267
- /**
268
- * Create a new tmux session with a default window named "default" in home directory
269
- */
270
- export async function createSession(name) {
271
- const homeDir = process.env.HOME || "~";
272
- await executeTmux(["new-session", "-d", "-s", name, "-n", "default", "-c", homeDir]);
273
- const session = await findSessionByName(name);
274
- if (!session) {
275
- return null;
276
- }
277
- // Disable automatic window renaming for all windows in the session (session target works; ':0' does not)
278
- await executeTmux(["set-window-option", "-t", session.id, "automatic-rename", "off"]);
279
- return session;
280
- }
281
- /**
282
- * Expand tilde in path to home directory
283
- */
284
- export function expandTilde(path) {
285
- if (path.startsWith("~/")) {
286
- const homeDir = process.env.HOME || os.homedir();
287
- return path.replace("~", homeDir);
288
- }
289
- if (path === "~") {
290
- return process.env.HOME || os.homedir();
291
- }
292
- return path;
293
- }
294
- /**
295
- * Create a new window in a session with optional working directory and initial command
296
- */
297
- export async function createWindow(sessionId, name, options) {
298
- // Validate name uniqueness
299
- const isUnique = await isWindowNameUnique(sessionId, name);
300
- if (!isUnique) {
301
- throw new Error(`Terminal with name '${name}' already exists. Please choose a unique name.`);
302
- }
303
- // Build new-window command with optional working directory
304
- const args = ["new-window", "-t", sessionId, "-n", name];
305
- if (options?.workingDirectory) {
306
- // Expand tilde to home directory before passing to tmux
307
- const expandedPath = expandTilde(options.workingDirectory);
308
- args.push("-c", expandedPath);
309
- }
310
- await executeTmux(args);
311
- const windows = await listWindows(sessionId);
312
- const window = windows.find((window) => window.name === name);
313
- if (!window)
314
- return null;
315
- // Disable automatic window renaming
316
- await executeTmux(["set-window-option", "-t", window.id, "automatic-rename", "off"]);
317
- // Get the default pane created with the window
318
- const panes = await listPanes(window.id);
319
- const defaultPane = panes[0];
320
- let commandOutput = null;
321
- // If command is provided, execute it in the new pane
322
- if (options?.command && defaultPane) {
323
- commandOutput = (await sendText({
324
- paneId: defaultPane.id,
325
- text: options.command,
326
- pressEnter: true,
327
- return_output: {
328
- waitForSettled: true,
329
- maxWait: 120000,
330
- },
331
- }));
332
- }
333
- return {
334
- ...window,
335
- paneId: defaultPane?.id || "",
336
- output: commandOutput,
337
- };
338
- }
339
- /**
340
- * Execute a command in a new window with remain-on-exit enabled
341
- * This allows capturing output and exit code even after the command finishes
342
- *
343
- * @param sessionId - The tmux session ID
344
- * @param command - The command to execute (will be wrapped in bash -c)
345
- * @param workingDirectory - Directory to execute command in
346
- * @param maxWait - Maximum milliseconds to wait for command completion or stability
347
- * @returns Window ID, output, exit code (if finished), and whether process is still running
348
- */
349
- export async function executeCommand({ sessionId, command, workingDirectory, maxWait = 120000, }) {
350
- // Generate unique window name using timestamp
351
- const windowName = `cmd-${Date.now()}`;
352
- const expandedPath = expandTilde(workingDirectory);
353
- // Create window
354
- const args = ["new-window", "-t", sessionId, "-n", windowName, "-c", expandedPath];
355
- await executeTmux(args);
356
- const windows = await listWindows(sessionId);
357
- const window = windows.find((w) => w.name === windowName);
358
- if (!window) {
359
- throw new Error("Failed to create window for command execution");
360
- }
361
- // Enable remain-on-exit to keep window after command finishes
362
- await executeTmux(["set-window-option", "-t", window.id, "remain-on-exit", "on"]);
363
- // Disable automatic window renaming
364
- await executeTmux(["set-window-option", "-t", window.id, "automatic-rename", "off"]);
365
- // Store command and working directory as window options for later retrieval
366
- await executeTmux(["set-window-option", "-t", window.id, "@command", command]);
367
- await executeTmux(["set-window-option", "-t", window.id, "@working_directory", expandedPath]);
368
- // Get the pane
369
- const panes = await listPanes(window.id);
370
- const pane = panes[0];
371
- if (!pane) {
372
- throw new Error("No pane found in created window");
373
- }
374
- // Execute command via respawn-pane with bash -c wrapper
375
- // This ensures all bash features work (pipes, operators, etc.)
376
- const wrappedCommand = `bash -c 'cd "${expandedPath}" && ${command.replace(/'/g, "'\\''")} 2>&1; code=$?; echo ${EXIT_CODE_MARKER}$code; exit $code'`;
377
- await executeTmux(["respawn-pane", "-t", pane.id, "-k", wrappedCommand]);
378
- // Wait for command to finish or reach stability
379
- const startTime = Date.now();
380
- let output = "";
381
- let isDead = false;
382
- let exitCode = null;
383
- const emptyOutputGraceMs = Math.min(maxWait, 2000);
384
- while (Date.now() - startTime < maxWait) {
385
- // Check if pane is dead (command exited)
386
- const deadStatus = await executeTmux(["display-message", "-p", "-t", pane.id, "#{pane_dead}"]);
387
- isDead = deadStatus === "1";
388
- if (isDead) {
389
- // Command finished - capture output and exit code
390
- output = await capturePaneContent(pane.id, 1000, false);
391
- const extracted = extractExitCodeMarkerFromOutput(output);
392
- output = extracted.output;
393
- const exitCodeStr = await executeTmux([
394
- "display-message",
395
- "-p",
396
- "-t",
397
- pane.id,
398
- "#{pane_dead_status}",
399
- ]);
400
- const parsed = parseInt(exitCodeStr, 10);
401
- exitCode = Number.isFinite(parsed) ? parsed : extracted.exitCode;
402
- break;
403
- }
404
- // Check for stability (output hasn't changed)
405
- const currentOutput = await capturePaneContent(pane.id, 1000, false);
406
- if (currentOutput === output) {
407
- // Wait a bit more to confirm stability
408
- await new Promise((resolve) => setTimeout(resolve, 500));
409
- const confirmedOutput = await capturePaneContent(pane.id, 1000, false);
410
- if (confirmedOutput === currentOutput) {
411
- const elapsed = Date.now() - startTime;
412
- if (!confirmedOutput && elapsed < emptyOutputGraceMs) {
413
- continue;
414
- }
415
- // Stable - command is likely waiting for input or running steadily
416
- output = confirmedOutput;
417
- break;
418
- }
419
- }
420
- output = currentOutput;
421
- await new Promise((resolve) => setTimeout(resolve, 100));
422
- }
423
- // Final capture if we hit timeout
424
- if (!isDead && !output) {
425
- output = await capturePaneContent(pane.id, 1000, false);
426
- }
427
- return {
428
- windowId: window.id,
429
- paneId: pane.id,
430
- output,
431
- exitCode,
432
- isDead,
433
- };
434
- }
435
- /**
436
- * Kill a tmux session by ID
437
- */
438
- export async function killSession(sessionId) {
439
- await executeTmux(["kill-session", "-t", sessionId]);
440
- }
441
- /**
442
- * Kill a tmux window by ID
443
- */
444
- export async function killWindow(windowId) {
445
- await executeTmux(["kill-window", "-t", windowId]);
446
- }
447
- /**
448
- * Kill a tmux pane by ID
449
- */
450
- export async function killPane(paneId) {
451
- await executeTmux(["kill-pane", "-t", paneId]);
452
- }
453
- /**
454
- * Rename a tmux window by name or ID
455
- */
456
- export async function renameWindow(sessionId, windowNameOrId, newName) {
457
- // Validate new name is unique
458
- const isUnique = await isWindowNameUnique(sessionId, newName);
459
- if (!isUnique) {
460
- throw new Error(`Terminal with name '${newName}' already exists. Please choose a unique name.`);
461
- }
462
- // Check if windowNameOrId is a window ID (starts with @) or a name
463
- let windowId;
464
- if (windowNameOrId.startsWith("@")) {
465
- windowId = windowNameOrId;
466
- }
467
- else {
468
- // Resolve name to ID
469
- const window = await findWindowByName(sessionId, windowNameOrId);
470
- if (!window) {
471
- throw new Error(`Terminal '${windowNameOrId}' not found.`);
472
- }
473
- windowId = window.id;
474
- }
475
- await executeTmux(["rename-window", "-t", windowId, newName]);
476
- // Disable automatic renaming to preserve the manual name
477
- await executeTmux(["set-window-option", "-t", windowId, "automatic-rename", "off"]);
478
- }
479
- /**
480
- * Split a tmux pane horizontally or vertically
481
- */
482
- export async function splitPane(targetPaneId, direction = "vertical", size) {
483
- // Build the split-window command args
484
- const args = ["split-window"];
485
- // Add direction flag (-h for horizontal, -v for vertical)
486
- if (direction === "horizontal") {
487
- args.push("-h");
488
- }
489
- else {
490
- args.push("-v");
491
- }
492
- // Add target pane
493
- args.push("-t", targetPaneId);
494
- // Add size if specified (as percentage)
495
- if (size !== undefined && size > 0 && size < 100) {
496
- args.push("-p", size.toString());
497
- }
498
- // Execute the split command
499
- await executeTmux(args);
500
- // Get the window ID from the target pane to list all panes
501
- const windowInfo = await executeTmux([
502
- "display-message",
503
- "-p",
504
- "-t",
505
- targetPaneId,
506
- "#{window_id}",
507
- ]);
508
- // List all panes in the window to find the newly created one
509
- const panes = await listPanes(windowInfo);
510
- // The newest pane is typically the last one in the list
511
- return panes.length > 0 ? panes[panes.length - 1] : null;
512
- }
513
- // Map to track ongoing command executions
514
- const activeCommands = new Map();
515
- const startMarkerText = "TMUX_MCP_START";
516
- const endMarkerPrefix = "TMUX_MCP_DONE_";
517
- // Execute a command in a tmux pane and track its execution (OLD - for backward compat)
518
- export async function executeCommandLegacy(paneId, command, rawMode, noEnter) {
519
- // Generate unique ID for this command execution
520
- const commandId = uuidv4();
521
- let fullCommand;
522
- if (rawMode || noEnter) {
523
- fullCommand = command;
524
- }
525
- else {
526
- const endMarkerText = getEndMarkerText();
527
- fullCommand = `echo "${startMarkerText}"; ${command}; echo "${endMarkerText}"`;
528
- }
529
- // Store command in tracking map
530
- activeCommands.set(commandId, {
531
- id: commandId,
532
- paneId,
533
- command,
534
- status: "pending",
535
- startTime: new Date(),
536
- rawMode: rawMode || noEnter,
537
- });
538
- // Send the command to the tmux pane
539
- if (noEnter) {
540
- // Check if this is a special key or key combination
541
- // Special keys in tmux are typically capitalized or have special names
542
- const specialKeys = [
543
- "Up",
544
- "Down",
545
- "Left",
546
- "Right",
547
- "Escape",
548
- "Tab",
549
- "Enter",
550
- "Space",
551
- "BSpace",
552
- "Delete",
553
- "Home",
554
- "End",
555
- "PageUp",
556
- "PageDown",
557
- "F1",
558
- "F2",
559
- "F3",
560
- "F4",
561
- "F5",
562
- "F6",
563
- "F7",
564
- "F8",
565
- "F9",
566
- "F10",
567
- "F11",
568
- "F12",
569
- "BTab",
570
- ];
571
- // Split the command into parts to handle combinations like "C-b" or "M-x"
572
- const parts = fullCommand.split("-");
573
- const isSpecialKey = parts.length === 1 && specialKeys.includes(parts[0]);
574
- const isKeyCombo = parts.length > 1 &&
575
- (parts[0] === "C" || // Control
576
- parts[0] === "M" || // Meta/Alt
577
- parts[0] === "S"); // Shift
578
- if (isSpecialKey || isKeyCombo) {
579
- // Send special key or key combination as-is
580
- const args = ["send-keys", "-t", paneId];
581
- args.push(...fullCommand.split(" "));
582
- await executeTmux(args);
583
- }
584
- else {
585
- // For regular text, send each character individually to ensure proper processing
586
- // This handles both single characters (like 'q', 'f') and strings (like 'beam')
587
- for (const char of fullCommand) {
588
- await executeTmux(["send-keys", "-t", paneId, char]);
589
- }
590
- }
591
- }
592
- else {
593
- await executeTmux(["send-keys", "-t", paneId, fullCommand, "Enter"]);
594
- }
595
- return commandId;
596
- }
597
- export async function checkCommandStatus(commandId) {
598
- const command = activeCommands.get(commandId);
599
- if (!command)
600
- return null;
601
- if (command.status !== "pending")
602
- return command;
603
- const content = await capturePaneContent(command.paneId, 1000);
604
- if (command.rawMode) {
605
- command.result =
606
- "Status tracking unavailable for rawMode commands. Use capture-pane to monitor interactive apps instead.";
607
- return command;
608
- }
609
- // Find the last occurrence of the markers
610
- const startIndex = content.lastIndexOf(startMarkerText);
611
- const endIndex = content.lastIndexOf(endMarkerPrefix);
612
- if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
613
- command.result = "Command output could not be captured properly";
614
- return command;
615
- }
616
- // Extract exit code from the end marker line
617
- const endLine = content.substring(endIndex).split("\n")[0];
618
- const endMarkerRegex = new RegExp(`${endMarkerPrefix}(\\d+)`);
619
- const exitCodeMatch = endLine.match(endMarkerRegex);
620
- if (exitCodeMatch) {
621
- const exitCode = parseInt(exitCodeMatch[1], 10);
622
- command.status = exitCode === 0 ? "completed" : "error";
623
- command.exitCode = exitCode;
624
- // Extract output between the start and end markers
625
- const outputStart = startIndex + startMarkerText.length;
626
- const outputContent = content.substring(outputStart, endIndex).trim();
627
- command.result = outputContent.substring(outputContent.indexOf("\n") + 1).trim();
628
- // Update in map
629
- activeCommands.set(commandId, command);
630
- }
631
- return command;
632
- }
633
- // Get command by ID
634
- export function getCommand(commandId) {
635
- return activeCommands.get(commandId) || null;
636
- }
637
- // Get all active command IDs
638
- export function getActiveCommandIds() {
639
- return Array.from(activeCommands.keys());
640
- }
641
- // Clean up completed commands older than a certain time
642
- export function cleanupOldCommands(maxAgeMinutes = 60) {
643
- const now = new Date();
644
- for (const [id, command] of activeCommands.entries()) {
645
- const ageMinutes = (now.getTime() - command.startTime.getTime()) / (1000 * 60);
646
- if (command.status !== "pending" && ageMinutes > maxAgeMinutes) {
647
- activeCommands.delete(id);
648
- }
649
- }
650
- }
651
- function getEndMarkerText() {
652
- return shellConfig.type === "fish" ? `${endMarkerPrefix}$status` : `${endMarkerPrefix}$?`;
653
- }
654
- export async function list({ scope, target, }) {
655
- if (scope === "all") {
656
- const sessions = await listSessions();
657
- const sessionsWithDetails = [];
658
- for (const session of sessions) {
659
- const windows = await listWindows(session.id);
660
- const windowsWithPanes = [];
661
- for (const window of windows) {
662
- const panes = await listPanes(window.id);
663
- windowsWithPanes.push({
664
- ...window,
665
- paneDetails: panes,
666
- });
667
- }
668
- sessionsWithDetails.push({
669
- ...session,
670
- windowDetails: windowsWithPanes,
671
- });
672
- }
673
- return sessionsWithDetails;
674
- }
675
- if (scope === "sessions") {
676
- return listSessions();
677
- }
678
- if (scope === "session") {
679
- if (!target) {
680
- throw new Error("target is required for scope 'session'");
681
- }
682
- return listWindows(target);
683
- }
684
- if (scope === "window") {
685
- if (!target) {
686
- throw new Error("target is required for scope 'window'");
687
- }
688
- return listPanes(target);
689
- }
690
- if (scope === "pane") {
691
- if (!target) {
692
- throw new Error("target is required for scope 'pane'");
693
- }
694
- const windowId = await executeTmux(["display-message", "-p", "-t", target, "#{window_id}"]);
695
- const panes = await listPanes(windowId);
696
- const pane = panes.find((p) => p.id === target);
697
- if (!pane) {
698
- throw new Error(`Pane not found: ${target}`);
699
- }
700
- return pane;
701
- }
702
- throw new Error(`Invalid scope: ${scope}`);
703
- }
704
- export async function kill({ scope, target }) {
705
- if (scope === "session") {
706
- return killSession(target);
707
- }
708
- if (scope === "window") {
709
- return killWindow(target);
710
- }
711
- if (scope === "pane") {
712
- return killPane(target);
713
- }
714
- throw new Error(`Invalid scope: ${scope}`);
715
- }
716
- export async function executeShellCommand({ paneId, command, timeout = 30000, }) {
717
- const commandId = uuidv4();
718
- const endMarkerText = getEndMarkerText();
719
- const fullCommand = `echo "${startMarkerText}"; ${command}; echo "${endMarkerText}"`;
720
- activeCommands.set(commandId, {
721
- id: commandId,
722
- paneId,
723
- command,
724
- status: "pending",
725
- startTime: new Date(),
726
- rawMode: false,
727
- });
728
- await executeTmux(["send-keys", "-t", paneId, fullCommand, "Enter"]);
729
- // Poll for completion
730
- const startTime = Date.now();
731
- const pollInterval = 100;
732
- while (Date.now() - startTime < timeout) {
733
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
734
- const result = await checkCommandStatus(commandId);
735
- if (result && result.status !== "pending") {
736
- // Cleanup
737
- activeCommands.delete(commandId);
738
- return {
739
- command: result.command,
740
- status: result.status,
741
- exitCode: result.exitCode,
742
- output: result.result || "",
743
- };
744
- }
745
- }
746
- // Timeout
747
- activeCommands.delete(commandId);
748
- throw new Error(`Command timed out after ${timeout}ms. Use capture-pane to check pane state.`);
749
- }
750
- export async function sendKeys({ paneId, keys, repeat = 1, return_output, }) {
751
- // Repeat the key press the specified number of times
752
- for (let i = 0; i < repeat; i++) {
753
- // Raw pass-through, no validation or processing
754
- const args = ["send-keys", "-t", paneId];
755
- args.push(...keys.split(" "));
756
- await executeTmux(args);
757
- }
758
- // If return_output is requested, wait and capture pane content
759
- if (return_output) {
760
- const lines = return_output.lines || 200;
761
- const waitForSettled = return_output.waitForSettled ?? true;
762
- const maxWait = return_output.maxWait ?? 120000; // 2 minutes default
763
- if (waitForSettled) {
764
- return waitForPaneActivityToSettle(paneId, maxWait, lines);
765
- }
766
- else {
767
- return capturePaneContent(paneId, lines, false);
768
- }
769
- }
770
- return null;
771
- }
772
- export async function waitForPaneActivityToSettle(paneId, maxWait, lines) {
773
- const settleTime = 1000; // Hardcoded debounce
774
- const pollInterval = 100; // Poll every 100ms
775
- let lastContent = "";
776
- let lastChangeTime = Date.now();
777
- const startTime = Date.now();
778
- while (true) {
779
- const elapsed = Date.now() - startTime;
780
- if (elapsed >= maxWait) {
781
- // Timeout - return what we have
782
- return lastContent;
783
- }
784
- const content = await capturePaneContent(paneId, lines, false);
785
- if (content !== lastContent) {
786
- // Activity detected - reset settle timer
787
- lastContent = content;
788
- lastChangeTime = Date.now();
789
- }
790
- else if (Date.now() - lastChangeTime >= settleTime) {
791
- // No changes for settleTime ms - settled!
792
- return content;
793
- }
794
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
795
- }
796
- }
797
- export async function sendText({ paneId, text, pressEnter = false, return_output, }) {
798
- // Send each character with -l flag for literal interpretation
799
- // Using execFile avoids shell interpretation of special characters like ; | & $
800
- for (const char of text) {
801
- await executeTmux(["send-keys", "-l", "-t", paneId, char]);
802
- }
803
- if (pressEnter) {
804
- await new Promise((resolve) => setTimeout(resolve, 300));
805
- await executeTmux(["send-keys", "-t", paneId, "Enter"]);
806
- }
807
- // If return_output is requested, wait and capture pane content
808
- if (return_output) {
809
- const lines = return_output.lines || 200;
810
- const waitForSettled = return_output.waitForSettled ?? true;
811
- const maxWait = return_output.maxWait ?? 120000; // 2 minutes default
812
- if (waitForSettled) {
813
- return waitForPaneActivityToSettle(paneId, maxWait, lines);
814
- }
815
- else {
816
- return capturePaneContent(paneId, lines, false);
817
- }
818
- }
819
- return null;
820
- }
821
- //# sourceMappingURL=tmux.js.map