claude-remote-cli 3.0.6 → 3.1.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.
@@ -1,45 +1,18 @@
1
- import pty from 'node-pty';
2
1
  import crypto from 'node:crypto';
3
2
  import fs from 'node:fs';
4
- import os from 'node:os';
5
3
  import path from 'node:path';
6
4
  import { execFile } from 'node:child_process';
7
5
  import { promisify } from 'node:util';
8
- import { readMeta, writeMeta } from './config.js';
6
+ import { AGENT_COMMANDS, AGENT_CONTINUE_ARGS, AGENT_YOLO_ARGS } from './types.js';
7
+ import { createPtySession } from './pty-handler.js';
8
+ import { createSdkSession, killSdkSession, sendMessage as sdkSendMessage, handlePermission as sdkHandlePermission, serializeSdkSession, restoreSdkSession } from './sdk-handler.js';
9
9
  const execFileAsync = promisify(execFile);
10
- const AGENT_COMMANDS = {
11
- claude: 'claude',
12
- codex: 'codex',
13
- };
14
- const AGENT_CONTINUE_ARGS = {
15
- claude: ['--continue'],
16
- codex: ['resume', '--last'],
17
- };
18
- const AGENT_YOLO_ARGS = {
19
- claude: ['--dangerously-skip-permissions'],
20
- codex: ['--full-auto'],
21
- };
22
10
  const STALE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
23
- function generateTmuxSessionName(displayName, id) {
24
- const sanitized = displayName.replace(/[^a-zA-Z0-9-]/g, '-').replace(/-+/g, '-').slice(0, 30);
25
- return `crc-${sanitized}-${id.slice(0, 8)}`;
26
- }
27
- function resolveTmuxSpawn(command, args, tmuxSessionName) {
28
- return {
29
- command: 'tmux',
30
- args: [
31
- '-u', 'new-session', '-s', tmuxSessionName, '--', command, ...args,
32
- // ';' tokens are tmux command separators — parsed at the top level before
33
- // dispatching to new-session, not passed as argv to `command`.
34
- ';', 'set', 'set-clipboard', 'on',
35
- ';', 'set', 'allow-passthrough', 'on',
36
- ';', 'set', 'mode-keys', 'vi',
37
- ],
38
- };
39
- }
11
+ const SDK_IDLE_CHECK_INTERVAL_MS = 60 * 1000; // 60 seconds
12
+ const SDK_MAX_IDLE_MS = 30 * 60 * 1000; // 30 minutes
13
+ const SDK_MAX_IDLE_SESSIONS = 5;
40
14
  // In-memory registry: id -> Session
41
15
  const sessions = new Map();
42
- const IDLE_TIMEOUT_MS = 5000;
43
16
  let terminalCounter = 0;
44
17
  const idleChangeCallbacks = [];
45
18
  function onIdleChange(cb) {
@@ -47,214 +20,81 @@ function onIdleChange(cb) {
47
20
  }
48
21
  function create({ id: providedId, type, agent = 'claude', repoName, repoPath, cwd, root, worktreeName, branchName, displayName, command, args = [], cols = 80, rows = 24, configPath, useTmux: paramUseTmux, tmuxSessionName: paramTmuxSessionName, initialScrollback, restored: paramRestored, needsBranchRename: paramNeedsBranchRename, branchRenamePrompt: paramBranchRenamePrompt }) {
49
22
  const id = providedId || crypto.randomBytes(8).toString('hex');
50
- const createdAt = new Date().toISOString();
51
- const resolvedCommand = command || AGENT_COMMANDS[agent];
52
- // Strip CLAUDECODE env var to allow spawning claude inside a claude-managed server
53
- const env = Object.assign({}, process.env);
54
- delete env.CLAUDECODE;
55
- const useTmux = !command && !!paramUseTmux;
56
- let spawnCommand = resolvedCommand;
57
- let spawnArgs = args;
58
- const tmuxSessionName = paramTmuxSessionName || (useTmux ? generateTmuxSessionName(displayName || repoName || 'session', id) : '');
59
- if (useTmux) {
60
- const tmux = resolveTmuxSpawn(resolvedCommand, args, tmuxSessionName);
61
- spawnCommand = tmux.command;
62
- spawnArgs = tmux.args;
23
+ // Dispatch: if agent is claude, no custom command, try SDK first
24
+ if (agent === 'claude' && !command) {
25
+ const sdkResult = createSdkSession({
26
+ id,
27
+ type,
28
+ agent,
29
+ repoName,
30
+ repoPath,
31
+ cwd,
32
+ root,
33
+ worktreeName,
34
+ branchName,
35
+ displayName,
36
+ }, sessions, idleChangeCallbacks);
37
+ if (!('fallback' in sdkResult)) {
38
+ return { ...sdkResult.result, pid: undefined, needsBranchRename: false };
39
+ }
40
+ // SDK init failed — fall through to PTY
63
41
  }
64
- // Wrap the spawn command to trap SIGPIPE in the child shell.
65
- // Without this, piped bash commands (e.g. `cmd | grep | tail`) run by
66
- // Claude Code inside the PTY can generate SIGPIPE when the reading end
67
- // of the pipe closes, which propagates up and kills the PTY session.
68
- // Wrapping with `trap '' PIPE; exec ...` makes the shell ignore SIGPIPE
69
- // before exec'ing the agent, so the agent inherits SIG_IGN for PIPE.
70
- const wrappedCommand = '/bin/bash';
71
- const wrappedArgs = ['-c', `trap '' PIPE; exec ${spawnCommand} ${spawnArgs.map(a => `'${a.replace(/'/g, "'\\''")}'`).join(' ')}`];
72
- const ptyProcess = pty.spawn(wrappedCommand, wrappedArgs, {
73
- name: 'xterm-256color',
74
- cols,
75
- rows,
76
- cwd: cwd || repoPath,
77
- env,
78
- });
79
- // Scrollback buffer: stores all PTY output so we can replay on WebSocket (re)connect
80
- const scrollback = initialScrollback ? [...initialScrollback] : [];
81
- let scrollbackBytes = initialScrollback ? initialScrollback.reduce((sum, s) => sum + s.length, 0) : 0;
82
- const MAX_SCROLLBACK = 256 * 1024; // 256KB max
83
- const resolvedCwd = cwd || repoPath;
84
- const session = {
42
+ // PTY path: codex, terminal, custom command, or SDK fallback
43
+ const ptyParams = {
85
44
  id,
86
- type: type || 'worktree',
45
+ type,
87
46
  agent,
88
- root: root || '',
89
- repoName: repoName || '',
47
+ repoName,
90
48
  repoPath,
91
- worktreeName: worktreeName || '',
92
- branchName: branchName || worktreeName || '',
93
- displayName: displayName || worktreeName || repoName || '',
94
- pty: ptyProcess,
95
- createdAt,
96
- lastActivity: createdAt,
97
- scrollback,
98
- idle: false,
99
- cwd: resolvedCwd,
100
- customCommand: command || null,
101
- useTmux,
102
- tmuxSessionName,
103
- onPtyReplacedCallbacks: [],
104
- status: 'active',
105
- restored: paramRestored || false,
106
- needsBranchRename: paramNeedsBranchRename || false,
107
- branchRenamePrompt: paramBranchRenamePrompt || '',
49
+ cwd,
50
+ root,
51
+ worktreeName,
52
+ branchName,
53
+ displayName,
54
+ command,
55
+ args,
56
+ cols,
57
+ rows,
58
+ configPath,
59
+ useTmux: paramUseTmux,
60
+ tmuxSessionName: paramTmuxSessionName,
61
+ initialScrollback,
62
+ restored: paramRestored,
108
63
  };
109
- sessions.set(id, session);
110
- // Load existing metadata to preserve a previously-set displayName
111
- if (configPath && worktreeName) {
112
- const existing = readMeta(configPath, repoPath);
113
- if (existing && existing.displayName) {
114
- session.displayName = existing.displayName;
115
- }
116
- writeMeta(configPath, { worktreePath: repoPath, displayName: session.displayName, lastActivity: createdAt });
64
+ const { session: ptySession, result } = createPtySession(ptyParams, sessions, idleChangeCallbacks);
65
+ if (paramNeedsBranchRename) {
66
+ ptySession.needsBranchRename = true;
117
67
  }
118
- let metaFlushTimer = null;
119
- let idleTimer = null;
120
- function resetIdleTimer() {
121
- if (session.idle) {
122
- session.idle = false;
123
- for (const cb of idleChangeCallbacks)
124
- cb(session.id, false);
125
- }
126
- if (idleTimer)
127
- clearTimeout(idleTimer);
128
- idleTimer = setTimeout(() => {
129
- if (!session.idle) {
130
- session.idle = true;
131
- for (const cb of idleChangeCallbacks)
132
- cb(session.id, true);
133
- }
134
- }, IDLE_TIMEOUT_MS);
68
+ if (paramBranchRenamePrompt) {
69
+ ptySession.branchRenamePrompt = paramBranchRenamePrompt;
135
70
  }
136
- const continueArgs = AGENT_CONTINUE_ARGS[agent];
137
- function attachHandlers(proc, canRetry) {
138
- const spawnTime = Date.now();
139
- // Clear restored flag after 3s of running — means the PTY is healthy
140
- const restoredClearTimer = session.restored ? setTimeout(() => { session.restored = false; }, 3000) : null;
141
- proc.onData((data) => {
142
- session.lastActivity = new Date().toISOString();
143
- resetIdleTimer();
144
- scrollback.push(data);
145
- scrollbackBytes += data.length;
146
- // Trim oldest entries if over limit
147
- while (scrollbackBytes > MAX_SCROLLBACK && scrollback.length > 1) {
148
- scrollbackBytes -= scrollback.shift().length;
149
- }
150
- if (configPath && worktreeName && !metaFlushTimer) {
151
- metaFlushTimer = setTimeout(() => {
152
- metaFlushTimer = null;
153
- writeMeta(configPath, { worktreePath: repoPath, displayName: session.displayName, lastActivity: session.lastActivity });
154
- }, 5000);
155
- }
156
- });
157
- proc.onExit(() => {
158
- // If continue args failed quickly, retry without them.
159
- // Exit code is intentionally not checked: tmux wrapping exits 0 even
160
- // when the inner command (e.g. claude --continue) fails, because the
161
- // tmux client doesn't propagate inner exit codes. The 3-second window
162
- // is the primary heuristic — no user quits a session that fast.
163
- if (canRetry && (Date.now() - spawnTime) < 3000) {
164
- const retryArgs = args.filter(a => !continueArgs.includes(a));
165
- const retryNotice = '\r\n[claude-remote-cli] --continue not available; starting new session...\r\n';
166
- scrollback.length = 0;
167
- scrollbackBytes = 0;
168
- scrollback.push(retryNotice);
169
- scrollbackBytes = retryNotice.length;
170
- let retryCommand = resolvedCommand;
171
- let retrySpawnArgs = retryArgs;
172
- if (useTmux && tmuxSessionName) {
173
- const retryTmuxName = tmuxSessionName + '-retry';
174
- session.tmuxSessionName = retryTmuxName;
175
- const tmux = resolveTmuxSpawn(resolvedCommand, retryArgs, retryTmuxName);
176
- retryCommand = tmux.command;
177
- retrySpawnArgs = tmux.args;
178
- }
179
- let retryPty;
180
- try {
181
- // Wrap retry spawn with SIGPIPE trap (same as initial spawn)
182
- const retryWrapped = ['-c', `trap '' PIPE; exec ${retryCommand} ${retrySpawnArgs.map(a => `'${a.replace(/'/g, "'\\''")}'`).join(' ')}`];
183
- retryPty = pty.spawn('/bin/bash', retryWrapped, {
184
- name: 'xterm-256color',
185
- cols,
186
- rows,
187
- cwd: cwd || repoPath,
188
- env,
189
- });
190
- }
191
- catch {
192
- // Retry spawn failed — fall through to normal exit cleanup
193
- if (restoredClearTimer)
194
- clearTimeout(restoredClearTimer);
195
- if (idleTimer)
196
- clearTimeout(idleTimer);
197
- if (metaFlushTimer)
198
- clearTimeout(metaFlushTimer);
199
- sessions.delete(id);
200
- return;
201
- }
202
- session.pty = retryPty;
203
- for (const cb of session.onPtyReplacedCallbacks)
204
- cb(retryPty);
205
- attachHandlers(retryPty, false);
206
- return;
207
- }
208
- if (restoredClearTimer)
209
- clearTimeout(restoredClearTimer);
210
- // If PTY exited and this is a restored session, mark disconnected rather than delete
211
- if (session.restored) {
212
- session.status = 'disconnected';
213
- session.restored = false; // clear so user-initiated kills can delete normally
214
- if (idleTimer)
215
- clearTimeout(idleTimer);
216
- if (metaFlushTimer)
217
- clearTimeout(metaFlushTimer);
218
- return;
219
- }
220
- if (idleTimer)
221
- clearTimeout(idleTimer);
222
- if (metaFlushTimer)
223
- clearTimeout(metaFlushTimer);
224
- if (configPath && worktreeName) {
225
- writeMeta(configPath, { worktreePath: repoPath, displayName: session.displayName, lastActivity: session.lastActivity });
226
- }
227
- sessions.delete(id);
228
- const tmpDir = path.join(os.tmpdir(), 'claude-remote-cli', id);
229
- fs.rm(tmpDir, { recursive: true, force: true }, () => { });
230
- });
231
- }
232
- attachHandlers(ptyProcess, continueArgs.some(a => args.includes(a)));
233
- return { id, type: session.type, agent: session.agent, root: session.root, repoName: session.repoName, repoPath, worktreeName: session.worktreeName, branchName: session.branchName, displayName: session.displayName, pid: ptyProcess.pid, createdAt, lastActivity: createdAt, idle: false, cwd: resolvedCwd, customCommand: command || null, useTmux, tmuxSessionName, status: 'active' };
71
+ return { ...result, needsBranchRename: !!ptySession.needsBranchRename };
234
72
  }
235
73
  function get(id) {
236
74
  return sessions.get(id);
237
75
  }
238
76
  function list() {
239
77
  return Array.from(sessions.values())
240
- .map(({ id, type, agent, root, repoName, repoPath, worktreeName, branchName, displayName, createdAt, lastActivity, idle, cwd, customCommand, useTmux, tmuxSessionName, status }) => ({
241
- id,
242
- type,
243
- agent,
244
- root,
245
- repoName,
246
- repoPath,
247
- worktreeName,
248
- branchName,
249
- displayName,
250
- createdAt,
251
- lastActivity,
252
- idle,
253
- cwd,
254
- customCommand,
255
- useTmux,
256
- tmuxSessionName,
257
- status,
78
+ .map((s) => ({
79
+ id: s.id,
80
+ type: s.type,
81
+ agent: s.agent,
82
+ mode: s.mode,
83
+ root: s.root,
84
+ repoName: s.repoName,
85
+ repoPath: s.repoPath,
86
+ worktreeName: s.worktreeName,
87
+ branchName: s.branchName,
88
+ displayName: s.displayName,
89
+ createdAt: s.createdAt,
90
+ lastActivity: s.lastActivity,
91
+ idle: s.idle,
92
+ cwd: s.cwd,
93
+ customCommand: s.customCommand,
94
+ useTmux: s.mode === 'pty' ? s.useTmux : false,
95
+ tmuxSessionName: s.mode === 'pty' ? s.tmuxSessionName : '',
96
+ status: s.status,
97
+ needsBranchRename: s.mode === 'pty' ? !!s.needsBranchRename : false,
258
98
  }))
259
99
  .sort((a, b) => b.lastActivity.localeCompare(a.lastActivity));
260
100
  }
@@ -270,20 +110,25 @@ function kill(id) {
270
110
  if (!session) {
271
111
  throw new Error(`Session not found: ${id}`);
272
112
  }
273
- try {
274
- session.pty.kill('SIGTERM');
275
- }
276
- catch {
277
- // PTY may already be dead (e.g. disconnected sessions) — still delete from registry
113
+ if (session.mode === 'pty') {
114
+ try {
115
+ session.pty.kill('SIGTERM');
116
+ }
117
+ catch {
118
+ // PTY may already be dead (e.g. disconnected sessions) — still delete from registry
119
+ }
120
+ if (session.tmuxSessionName) {
121
+ execFile('tmux', ['kill-session', '-t', session.tmuxSessionName], () => { });
122
+ }
278
123
  }
279
- if (session.tmuxSessionName) {
280
- execFile('tmux', ['kill-session', '-t', session.tmuxSessionName], () => { });
124
+ else if (session.mode === 'sdk') {
125
+ killSdkSession(id);
281
126
  }
282
127
  sessions.delete(id);
283
128
  }
284
129
  function killAllTmuxSessions() {
285
130
  for (const session of sessions.values()) {
286
- if (session.tmuxSessionName) {
131
+ if (session.mode === 'pty' && session.tmuxSessionName) {
287
132
  execFile('tmux', ['kill-session', '-t', session.tmuxSessionName], () => { });
288
133
  }
289
134
  }
@@ -293,14 +138,25 @@ function resize(id, cols, rows) {
293
138
  if (!session) {
294
139
  throw new Error(`Session not found: ${id}`);
295
140
  }
296
- session.pty.resize(cols, rows);
141
+ if (session.mode === 'pty') {
142
+ session.pty.resize(cols, rows);
143
+ }
144
+ // SDK sessions don't support resize (no PTY)
297
145
  }
298
146
  function write(id, data) {
299
147
  const session = sessions.get(id);
300
148
  if (!session) {
301
149
  throw new Error(`Session not found: ${id}`);
302
150
  }
303
- session.pty.write(data);
151
+ if (session.mode === 'pty') {
152
+ session.pty.write(data);
153
+ }
154
+ else if (session.mode === 'sdk') {
155
+ sdkSendMessage(id, data);
156
+ }
157
+ }
158
+ function handlePermission(id, requestId, approved) {
159
+ sdkHandlePermission(id, requestId, approved);
304
160
  }
305
161
  function findRepoSession(repoPath) {
306
162
  return list().find((s) => s.type === 'repo' && s.repoPath === repoPath);
@@ -311,33 +167,40 @@ function nextTerminalName() {
311
167
  function serializeAll(configDir) {
312
168
  const scrollbackDirPath = path.join(configDir, 'scrollback');
313
169
  fs.mkdirSync(scrollbackDirPath, { recursive: true });
314
- const serialized = [];
170
+ const serializedPty = [];
171
+ const serializedSdk = [];
315
172
  for (const session of sessions.values()) {
316
- // Write scrollback to disk
317
- const scrollbackPath = path.join(scrollbackDirPath, session.id + '.buf');
318
- fs.writeFileSync(scrollbackPath, session.scrollback.join(''), 'utf-8');
319
- serialized.push({
320
- id: session.id,
321
- type: session.type,
322
- agent: session.agent,
323
- root: session.root,
324
- repoName: session.repoName,
325
- repoPath: session.repoPath,
326
- worktreeName: session.worktreeName,
327
- branchName: session.branchName,
328
- displayName: session.displayName,
329
- createdAt: session.createdAt,
330
- lastActivity: session.lastActivity,
331
- useTmux: session.useTmux,
332
- tmuxSessionName: session.tmuxSessionName,
333
- customCommand: session.customCommand,
334
- cwd: session.cwd,
335
- });
173
+ if (session.mode === 'pty') {
174
+ // Write scrollback to disk
175
+ const scrollbackPath = path.join(scrollbackDirPath, session.id + '.buf');
176
+ fs.writeFileSync(scrollbackPath, session.scrollback.join(''), 'utf-8');
177
+ serializedPty.push({
178
+ id: session.id,
179
+ type: session.type,
180
+ agent: session.agent,
181
+ root: session.root,
182
+ repoName: session.repoName,
183
+ repoPath: session.repoPath,
184
+ worktreeName: session.worktreeName,
185
+ branchName: session.branchName,
186
+ displayName: session.displayName,
187
+ createdAt: session.createdAt,
188
+ lastActivity: session.lastActivity,
189
+ useTmux: session.useTmux,
190
+ tmuxSessionName: session.tmuxSessionName,
191
+ customCommand: session.customCommand,
192
+ cwd: session.cwd,
193
+ });
194
+ }
195
+ else if (session.mode === 'sdk') {
196
+ serializedSdk.push(serializeSdkSession(session));
197
+ }
336
198
  }
337
199
  const pending = {
338
200
  version: 1,
339
201
  timestamp: new Date().toISOString(),
340
- sessions: serialized,
202
+ sessions: serializedPty,
203
+ sdkSessions: serializedSdk.length > 0 ? serializedSdk : undefined,
341
204
  };
342
205
  fs.writeFileSync(path.join(configDir, 'pending-sessions.json'), JSON.stringify(pending, null, 2), 'utf-8');
343
206
  }
@@ -360,6 +223,7 @@ async function restoreFromDisk(configDir) {
360
223
  }
361
224
  const scrollbackDirPath = path.join(configDir, 'scrollback');
362
225
  let restored = 0;
226
+ // Restore PTY sessions
363
227
  for (const s of pending.sessions) {
364
228
  // Load scrollback from disk
365
229
  let initialScrollback;
@@ -436,6 +300,18 @@ async function restoreFromDisk(configDir) {
436
300
  }
437
301
  catch { /* ignore */ }
438
302
  }
303
+ // Restore SDK sessions (as disconnected — they can't resume a live process)
304
+ if (pending.sdkSessions) {
305
+ for (const sdkData of pending.sdkSessions) {
306
+ try {
307
+ restoreSdkSession(sdkData, sessions);
308
+ restored++;
309
+ }
310
+ catch {
311
+ console.error(`Failed to restore SDK session ${sdkData.id} (${sdkData.displayName})`);
312
+ }
313
+ }
314
+ }
439
315
  // Clean up
440
316
  try {
441
317
  fs.unlinkSync(pendingPath);
@@ -451,9 +327,55 @@ async function restoreFromDisk(configDir) {
451
327
  function activeTmuxSessionNames() {
452
328
  const names = new Set();
453
329
  for (const session of sessions.values()) {
454
- if (session.tmuxSessionName)
330
+ if (session.mode === 'pty' && session.tmuxSessionName)
455
331
  names.add(session.tmuxSessionName);
456
332
  }
457
333
  return names;
458
334
  }
459
- export { create, get, list, kill, killAllTmuxSessions, resize, updateDisplayName, write, onIdleChange, findRepoSession, nextTerminalName, serializeAll, restoreFromDisk, activeTmuxSessionNames, AGENT_COMMANDS, AGENT_CONTINUE_ARGS, AGENT_YOLO_ARGS, resolveTmuxSpawn, generateTmuxSessionName };
335
+ // SDK idle sweep: check every 60s, terminate SDK sessions idle > 30min, max 5 idle
336
+ let sdkIdleSweepTimer = null;
337
+ function startSdkIdleSweep() {
338
+ if (sdkIdleSweepTimer)
339
+ return;
340
+ sdkIdleSweepTimer = setInterval(() => {
341
+ const now = Date.now();
342
+ const sdkSessions = [];
343
+ for (const session of sessions.values()) {
344
+ if (session.mode === 'sdk') {
345
+ sdkSessions.push(session);
346
+ }
347
+ }
348
+ // Terminate sessions idle > 30 minutes
349
+ for (const session of sdkSessions) {
350
+ const lastActivity = new Date(session.lastActivity).getTime();
351
+ if (session.idle && (now - lastActivity) > SDK_MAX_IDLE_MS) {
352
+ console.log(`SDK idle sweep: terminating session ${session.id} (${session.displayName}) — idle for ${Math.round((now - lastActivity) / 60000)}min`);
353
+ try {
354
+ kill(session.id);
355
+ }
356
+ catch { /* already dead */ }
357
+ }
358
+ }
359
+ // LRU eviction: if more than 5 idle SDK sessions remain, evict oldest
360
+ const idleSdkSessions = Array.from(sessions.values())
361
+ .filter((s) => s.mode === 'sdk' && s.idle)
362
+ .sort((a, b) => a.lastActivity.localeCompare(b.lastActivity));
363
+ while (idleSdkSessions.length > SDK_MAX_IDLE_SESSIONS) {
364
+ const oldest = idleSdkSessions.shift();
365
+ console.log(`SDK idle sweep: evicting session ${oldest.id} (${oldest.displayName}) — LRU`);
366
+ try {
367
+ kill(oldest.id);
368
+ }
369
+ catch { /* already dead */ }
370
+ }
371
+ }, SDK_IDLE_CHECK_INTERVAL_MS);
372
+ }
373
+ function stopSdkIdleSweep() {
374
+ if (sdkIdleSweepTimer) {
375
+ clearInterval(sdkIdleSweepTimer);
376
+ sdkIdleSweepTimer = null;
377
+ }
378
+ }
379
+ // Re-export pty-handler utilities for backward compatibility
380
+ export { generateTmuxSessionName, resolveTmuxSpawn } from './pty-handler.js';
381
+ export { create, get, list, kill, killAllTmuxSessions, resize, updateDisplayName, write, handlePermission, onIdleChange, findRepoSession, nextTerminalName, serializeAll, restoreFromDisk, activeTmuxSessionNames, startSdkIdleSweep, stopSdkIdleSweep, AGENT_COMMANDS, AGENT_CONTINUE_ARGS, AGENT_YOLO_ARGS };
@@ -1,3 +1,16 @@
1
+ // Agent command records (shared by PTY and SDK handlers)
2
+ export const AGENT_COMMANDS = {
3
+ claude: 'claude',
4
+ codex: 'codex',
5
+ };
6
+ export const AGENT_CONTINUE_ARGS = {
7
+ claude: ['--continue'],
8
+ codex: ['resume', '--last'],
9
+ };
10
+ export const AGENT_YOLO_ARGS = {
11
+ claude: ['--dangerously-skip-permissions'],
12
+ codex: ['--full-auto'],
13
+ };
1
14
  export const MOUNTAIN_NAMES = [
2
15
  'everest', 'kilimanjaro', 'denali', 'fuji', 'rainier', 'matterhorn',
3
16
  'elbrus', 'aconcagua', 'kangchenjunga', 'lhotse', 'makalu', 'cho-oyu',