claude-remote 0.4.3 → 0.4.5
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/package.json +1 -1
- package/server.js +220 -168
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -5,7 +5,7 @@ const os = require('os');
|
|
|
5
5
|
const pty = require('node-pty');
|
|
6
6
|
const { WebSocketServer, WebSocket } = require('ws');
|
|
7
7
|
const crypto = require('crypto');
|
|
8
|
-
const { execSync, spawn } = require('child_process');
|
|
8
|
+
const { execSync, spawn } = require('child_process');
|
|
9
9
|
|
|
10
10
|
// --- CLI argument parsing ---
|
|
11
11
|
// Separate bridge args (CWD positional) from claude passthrough flags.
|
|
@@ -158,12 +158,12 @@ let CWD = _parsedCwd;
|
|
|
158
158
|
const CLAUDE_HOME = path.join(os.homedir(), '.claude');
|
|
159
159
|
const CLAUDE_STATE_FILE = path.join(os.homedir(), '.claude.json');
|
|
160
160
|
const PROJECTS_DIR = path.join(CLAUDE_HOME, 'projects');
|
|
161
|
-
const AUTH_HELLO_TIMEOUT_MS = 5000;
|
|
162
|
-
const WS_CLOSE_AUTH_FAILED = 4001;
|
|
163
|
-
const WS_CLOSE_AUTH_TIMEOUT = 4002;
|
|
164
|
-
const WS_CLOSE_REASON_AUTH_FAILED = 'auth_failed';
|
|
165
|
-
const WS_CLOSE_REASON_AUTH_TIMEOUT = 'auth_timeout';
|
|
166
|
-
const DEBUG_TTY_INPUT = process.env.CLAUDE_REMOTE_DEBUG_TTY_INPUT === '1';
|
|
161
|
+
const AUTH_HELLO_TIMEOUT_MS = 5000;
|
|
162
|
+
const WS_CLOSE_AUTH_FAILED = 4001;
|
|
163
|
+
const WS_CLOSE_AUTH_TIMEOUT = 4002;
|
|
164
|
+
const WS_CLOSE_REASON_AUTH_FAILED = 'auth_failed';
|
|
165
|
+
const WS_CLOSE_REASON_AUTH_TIMEOUT = 'auth_timeout';
|
|
166
|
+
const DEBUG_TTY_INPUT = process.env.CLAUDE_REMOTE_DEBUG_TTY_INPUT === '1';
|
|
167
167
|
|
|
168
168
|
// --- State ---
|
|
169
169
|
let claudeProc = null;
|
|
@@ -197,6 +197,7 @@ let turnState = {
|
|
|
197
197
|
let ttyInputForwarderAttached = false;
|
|
198
198
|
let ttyInputHandler = null;
|
|
199
199
|
let ttyResizeHandler = null;
|
|
200
|
+
let activeLinuxClipboardProc = null;
|
|
200
201
|
|
|
201
202
|
// --- Permission approval state ---
|
|
202
203
|
let approvalSeq = 0;
|
|
@@ -209,15 +210,27 @@ const PARTIAL_AUTO_ALLOW = new Set(['Read', 'Glob', 'Grep', 'Write', 'Edit']);
|
|
|
209
210
|
// --- Logging → file only (never pollute the terminal) ---
|
|
210
211
|
const LOG_FILE = path.join(os.homedir(), '.claude', 'bridge.log');
|
|
211
212
|
fs.writeFileSync(LOG_FILE, `--- Bridge started ${new Date().toISOString()} ---\n`);
|
|
212
|
-
function log(msg) {
|
|
213
|
-
const line = `[${new Date().toISOString()}] ${msg}\n`;
|
|
214
|
-
fs.appendFileSync(LOG_FILE, line);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function formatTtyInputChunk(chunk) {
|
|
218
|
-
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
219
|
-
return `len=${buf.length} hex=${buf.toString('hex')} base64=${buf.toString('base64')} utf8=${JSON.stringify(buf.toString('utf8'))}`;
|
|
220
|
-
}
|
|
213
|
+
function log(msg) {
|
|
214
|
+
const line = `[${new Date().toISOString()}] ${msg}\n`;
|
|
215
|
+
fs.appendFileSync(LOG_FILE, line);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function formatTtyInputChunk(chunk) {
|
|
219
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
220
|
+
return `len=${buf.length} hex=${buf.toString('hex')} base64=${buf.toString('base64')} utf8=${JSON.stringify(buf.toString('utf8'))}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function clearActiveLinuxClipboardProc(reason = '') {
|
|
224
|
+
if (!activeLinuxClipboardProc) return;
|
|
225
|
+
const { child, tool } = activeLinuxClipboardProc;
|
|
226
|
+
activeLinuxClipboardProc = null;
|
|
227
|
+
try {
|
|
228
|
+
child.kill('SIGTERM');
|
|
229
|
+
log(`Linux clipboard process terminated (${tool}) reason=${reason || 'cleanup'}`);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
log(`Linux clipboard process terminate error (${tool}): ${err.message}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
221
234
|
|
|
222
235
|
function wsLabel(ws) {
|
|
223
236
|
const clientId = ws && ws._clientInstanceId ? ` client=${ws._clientInstanceId}` : '';
|
|
@@ -250,6 +263,7 @@ function getTurnStatePayload() {
|
|
|
250
263
|
sessionId: turnState.sessionId,
|
|
251
264
|
version: turnState.version,
|
|
252
265
|
updatedAt: turnState.updatedAt,
|
|
266
|
+
reason: turnState.reason || '',
|
|
253
267
|
};
|
|
254
268
|
}
|
|
255
269
|
|
|
@@ -271,6 +285,7 @@ function setTurnState(phase, { sessionId = currentSessionId, reason = '', force
|
|
|
271
285
|
sessionId: normalizedSessionId,
|
|
272
286
|
version: ++turnStateVersion,
|
|
273
287
|
updatedAt: Date.now(),
|
|
288
|
+
reason,
|
|
274
289
|
};
|
|
275
290
|
|
|
276
291
|
log(`Turn state -> phase=${turnState.phase} session=${turnState.sessionId ?? 'null'} version=${turnState.version}${reason ? ` reason=${reason}` : ''}`);
|
|
@@ -278,19 +293,41 @@ function setTurnState(phase, { sessionId = currentSessionId, reason = '', force
|
|
|
278
293
|
return true;
|
|
279
294
|
}
|
|
280
295
|
|
|
296
|
+
function emitInterrupt(source) {
|
|
297
|
+
const interruptEvent = {
|
|
298
|
+
type: 'interrupt',
|
|
299
|
+
source,
|
|
300
|
+
timestamp: Date.now(),
|
|
301
|
+
uuid: crypto.randomUUID(),
|
|
302
|
+
};
|
|
303
|
+
const record = { seq: ++eventSeq, event: interruptEvent };
|
|
304
|
+
eventBuffer.push(record);
|
|
305
|
+
if (eventBuffer.length > EVENT_BUFFER_MAX) {
|
|
306
|
+
eventBuffer = eventBuffer.slice(-Math.round(EVENT_BUFFER_MAX * 0.8));
|
|
307
|
+
}
|
|
308
|
+
broadcast({ type: 'log_event', seq: record.seq, event: interruptEvent });
|
|
309
|
+
setTurnState('idle', { reason: `${source}_interrupt` });
|
|
310
|
+
}
|
|
311
|
+
|
|
281
312
|
function attachTtyForwarders() {
|
|
282
|
-
if (!isTTY || ttyInputForwarderAttached) return;
|
|
283
|
-
|
|
284
|
-
ttyInputHandler = (chunk) => {
|
|
285
|
-
if (DEBUG_TTY_INPUT) {
|
|
286
|
-
try {
|
|
287
|
-
log(`TTY input ${formatTtyInputChunk(chunk)}`);
|
|
288
|
-
} catch (err) {
|
|
289
|
-
log(`TTY input log error: ${err.message}`);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
if (claudeProc) claudeProc.write(chunk);
|
|
293
|
-
|
|
313
|
+
if (!isTTY || ttyInputForwarderAttached) return;
|
|
314
|
+
|
|
315
|
+
ttyInputHandler = (chunk) => {
|
|
316
|
+
if (DEBUG_TTY_INPUT) {
|
|
317
|
+
try {
|
|
318
|
+
log(`TTY input ${formatTtyInputChunk(chunk)}`);
|
|
319
|
+
} catch (err) {
|
|
320
|
+
log(`TTY input log error: ${err.message}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (claudeProc) claudeProc.write(chunk);
|
|
324
|
+
// Detect Ctrl+C (0x03) from local terminal and sync state
|
|
325
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
326
|
+
if (buf.includes(0x03) && turnState.phase === 'running') {
|
|
327
|
+
log('Terminal Ctrl+C detected — injecting interrupt event');
|
|
328
|
+
emitInterrupt('terminal');
|
|
329
|
+
}
|
|
330
|
+
};
|
|
294
331
|
ttyResizeHandler = () => {
|
|
295
332
|
if (claudeProc) claudeProc.resize(process.stdout.columns, process.stdout.rows);
|
|
296
333
|
};
|
|
@@ -644,77 +681,80 @@ function cleanupClientUploads(ws) {
|
|
|
644
681
|
}
|
|
645
682
|
}
|
|
646
683
|
|
|
647
|
-
function createTempImageFile(buffer, mediaType, uploadId) {
|
|
648
|
-
const isLinux = process.platform !== 'win32' && process.platform !== 'darwin';
|
|
649
|
-
const tmpDir = isLinux
|
|
650
|
-
? IMAGE_UPLOAD_DIR
|
|
651
|
-
: (process.env.CLAUDE_CODE_TMPDIR || os.tmpdir());
|
|
684
|
+
function createTempImageFile(buffer, mediaType, uploadId) {
|
|
685
|
+
const isLinux = process.platform !== 'win32' && process.platform !== 'darwin';
|
|
686
|
+
const tmpDir = isLinux
|
|
687
|
+
? IMAGE_UPLOAD_DIR
|
|
688
|
+
: (process.env.CLAUDE_CODE_TMPDIR || os.tmpdir());
|
|
652
689
|
const type = String(mediaType || 'image/png').toLowerCase();
|
|
653
690
|
const ext = type.includes('jpeg') || type.includes('jpg') ? '.jpg' : '.png';
|
|
654
691
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
655
692
|
const tmpFile = path.join(tmpDir, `bridge_upload_${uploadId}_${Date.now()}${ext}`);
|
|
656
|
-
fs.writeFileSync(tmpFile, buffer);
|
|
657
|
-
return tmpFile;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
function getLinuxClipboardTool() {
|
|
661
|
-
if (process.platform === 'win32' || process.platform === 'darwin') return null;
|
|
662
|
-
try {
|
|
663
|
-
execSync('command -v xclip >/dev/null 2>&1', {
|
|
664
|
-
stdio: 'ignore',
|
|
665
|
-
shell: '/bin/sh',
|
|
666
|
-
timeout: 2000,
|
|
667
|
-
});
|
|
668
|
-
return 'xclip';
|
|
669
|
-
} catch {}
|
|
670
|
-
try {
|
|
671
|
-
execSync('command -v wl-copy >/dev/null 2>&1', {
|
|
672
|
-
stdio: 'ignore',
|
|
673
|
-
shell: '/bin/sh',
|
|
674
|
-
timeout: 2000,
|
|
675
|
-
});
|
|
676
|
-
return 'wl-copy';
|
|
677
|
-
} catch {}
|
|
678
|
-
return null;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
function assertLinuxClipboardAvailable() {
|
|
682
|
-
const tool = getLinuxClipboardTool();
|
|
683
|
-
if (tool) return tool;
|
|
684
|
-
throw new Error('Linux image paste requires xclip or wl-copy on the server. Install one and try again.');
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
function startLinuxClipboardImage(tmpFile, mediaType) {
|
|
688
|
-
const type = String(mediaType || 'image/png').toLowerCase();
|
|
689
|
-
const tool = assertLinuxClipboardAvailable();
|
|
690
|
-
const imageBuffer = fs.readFileSync(tmpFile);
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
child
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
});
|
|
713
|
-
child.stdin.
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
693
|
+
fs.writeFileSync(tmpFile, buffer);
|
|
694
|
+
return tmpFile;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function getLinuxClipboardTool() {
|
|
698
|
+
if (process.platform === 'win32' || process.platform === 'darwin') return null;
|
|
699
|
+
try {
|
|
700
|
+
execSync('command -v xclip >/dev/null 2>&1', {
|
|
701
|
+
stdio: 'ignore',
|
|
702
|
+
shell: '/bin/sh',
|
|
703
|
+
timeout: 2000,
|
|
704
|
+
});
|
|
705
|
+
return 'xclip';
|
|
706
|
+
} catch {}
|
|
707
|
+
try {
|
|
708
|
+
execSync('command -v wl-copy >/dev/null 2>&1', {
|
|
709
|
+
stdio: 'ignore',
|
|
710
|
+
shell: '/bin/sh',
|
|
711
|
+
timeout: 2000,
|
|
712
|
+
});
|
|
713
|
+
return 'wl-copy';
|
|
714
|
+
} catch {}
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function assertLinuxClipboardAvailable() {
|
|
719
|
+
const tool = getLinuxClipboardTool();
|
|
720
|
+
if (tool) return tool;
|
|
721
|
+
throw new Error('Linux image paste requires xclip or wl-copy on the server. Install one and try again.');
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function startLinuxClipboardImage(tmpFile, mediaType) {
|
|
725
|
+
const type = String(mediaType || 'image/png').toLowerCase();
|
|
726
|
+
const tool = assertLinuxClipboardAvailable();
|
|
727
|
+
const imageBuffer = fs.readFileSync(tmpFile);
|
|
728
|
+
clearActiveLinuxClipboardProc('replace');
|
|
729
|
+
const args = tool === 'xclip'
|
|
730
|
+
? ['-selection', 'clipboard', '-t', type, '-i']
|
|
731
|
+
: ['--type', type];
|
|
732
|
+
const child = spawn(tool, args, {
|
|
733
|
+
detached: true,
|
|
734
|
+
stdio: ['pipe', 'ignore', 'pipe'],
|
|
735
|
+
});
|
|
736
|
+
activeLinuxClipboardProc = { child, tool };
|
|
737
|
+
let stderr = '';
|
|
738
|
+
child.on('error', (err) => {
|
|
739
|
+
log(`Linux clipboard process error (${tool}): ${err.message}`);
|
|
740
|
+
});
|
|
741
|
+
child.stderr.on('data', (chunk) => {
|
|
742
|
+
stderr += chunk.toString('utf8');
|
|
743
|
+
if (stderr.length > 2000) stderr = stderr.slice(-2000);
|
|
744
|
+
});
|
|
745
|
+
child.on('exit', (code, signal) => {
|
|
746
|
+
if (activeLinuxClipboardProc && activeLinuxClipboardProc.child === child) activeLinuxClipboardProc = null;
|
|
747
|
+
const extra = stderr.trim() ? ` stderr=${JSON.stringify(stderr.trim())}` : '';
|
|
748
|
+
log(`Linux clipboard process exited (${tool}) code=${code ?? 'null'} signal=${signal ?? 'null'}${extra}`);
|
|
749
|
+
});
|
|
750
|
+
child.stdin.on('error', (err) => {
|
|
751
|
+
if (err.code !== 'EPIPE') log(`Linux clipboard stdin error (${tool}): ${err.message}`);
|
|
752
|
+
});
|
|
753
|
+
child.stdin.end(imageBuffer);
|
|
754
|
+
child.unref();
|
|
755
|
+
log(`Linux clipboard process started (${tool}) pid=${child.pid ?? 'null'} type=${type} bytes=${imageBuffer.length}`);
|
|
756
|
+
return tool;
|
|
757
|
+
}
|
|
718
758
|
|
|
719
759
|
setInterval(() => {
|
|
720
760
|
const now = Date.now();
|
|
@@ -883,6 +923,14 @@ wss.on('connection', (ws, req) => {
|
|
|
883
923
|
// Raw terminal keystrokes from xterm.js in WebUI
|
|
884
924
|
if (claudeProc) claudeProc.write(msg.data);
|
|
885
925
|
break;
|
|
926
|
+
case 'interrupt': {
|
|
927
|
+
// User pressed stop button in app — send Ctrl+C to PTY
|
|
928
|
+
if (!claudeProc || turnState.phase !== 'running') break;
|
|
929
|
+
log(`Interrupt from ${wsLabel(ws)} — sending Ctrl+C to PTY`);
|
|
930
|
+
claudeProc.write('\x03');
|
|
931
|
+
emitInterrupt('app');
|
|
932
|
+
break;
|
|
933
|
+
}
|
|
886
934
|
case 'expect_clear':
|
|
887
935
|
// Plan mode option 1 triggers /clear inside Claude Code;
|
|
888
936
|
// client notifies us so we can detect the session switch.
|
|
@@ -950,25 +998,25 @@ wss.on('connection', (ws, req) => {
|
|
|
950
998
|
}
|
|
951
999
|
break;
|
|
952
1000
|
}
|
|
953
|
-
case 'image_upload_init': {
|
|
954
|
-
const uploadId = String(msg.uploadId || '');
|
|
955
|
-
if (!uploadId) {
|
|
956
|
-
sendUploadStatus(ws, '', 'error', { message: 'Missing uploadId' });
|
|
957
|
-
break;
|
|
958
|
-
}
|
|
959
|
-
cleanupImageUpload(uploadId);
|
|
960
|
-
if (process.platform !== 'win32' && process.platform !== 'darwin') {
|
|
961
|
-
try {
|
|
962
|
-
const tool = assertLinuxClipboardAvailable();
|
|
963
|
-
log(`Linux clipboard preflight OK: ${tool}`);
|
|
964
|
-
} catch (err) {
|
|
965
|
-
sendUploadStatus(ws, uploadId, 'error', { message: err.message });
|
|
966
|
-
break;
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
pendingImageUploads.set(uploadId, {
|
|
970
|
-
id: uploadId,
|
|
971
|
-
owner: ws,
|
|
1001
|
+
case 'image_upload_init': {
|
|
1002
|
+
const uploadId = String(msg.uploadId || '');
|
|
1003
|
+
if (!uploadId) {
|
|
1004
|
+
sendUploadStatus(ws, '', 'error', { message: 'Missing uploadId' });
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
cleanupImageUpload(uploadId);
|
|
1008
|
+
if (process.platform !== 'win32' && process.platform !== 'darwin') {
|
|
1009
|
+
try {
|
|
1010
|
+
const tool = assertLinuxClipboardAvailable();
|
|
1011
|
+
log(`Linux clipboard preflight OK: ${tool}`);
|
|
1012
|
+
} catch (err) {
|
|
1013
|
+
sendUploadStatus(ws, uploadId, 'error', { message: err.message });
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
pendingImageUploads.set(uploadId, {
|
|
1018
|
+
id: uploadId,
|
|
1019
|
+
owner: ws,
|
|
972
1020
|
mediaType: msg.mediaType || 'image/png',
|
|
973
1021
|
name: msg.name || 'image',
|
|
974
1022
|
totalBytes: Number.isFinite(msg.totalBytes) ? msg.totalBytes : 0,
|
|
@@ -1066,15 +1114,15 @@ wss.on('connection', (ws, req) => {
|
|
|
1066
1114
|
if (!upload || !upload.tmpFile) {
|
|
1067
1115
|
sendUploadStatus(ws, uploadId, 'error', { message: 'Upload not ready' });
|
|
1068
1116
|
break;
|
|
1069
|
-
}
|
|
1070
|
-
try {
|
|
1071
|
-
handlePreparedImageUpload({
|
|
1072
|
-
tmpFile: upload.tmpFile,
|
|
1073
|
-
mediaType: upload.mediaType,
|
|
1074
|
-
text: msg.text || '',
|
|
1075
|
-
logLabel: upload.name || uploadId,
|
|
1076
|
-
onCleanup: () => cleanupImageUpload(uploadId),
|
|
1077
|
-
});
|
|
1117
|
+
}
|
|
1118
|
+
try {
|
|
1119
|
+
handlePreparedImageUpload({
|
|
1120
|
+
tmpFile: upload.tmpFile,
|
|
1121
|
+
mediaType: upload.mediaType,
|
|
1122
|
+
text: msg.text || '',
|
|
1123
|
+
logLabel: upload.name || uploadId,
|
|
1124
|
+
onCleanup: () => cleanupImageUpload(uploadId),
|
|
1125
|
+
});
|
|
1078
1126
|
upload.submitted = true;
|
|
1079
1127
|
upload.updatedAt = Date.now();
|
|
1080
1128
|
setTurnState('running', { reason: 'image_submit' });
|
|
@@ -1707,7 +1755,7 @@ function handlePreparedImageUpload({ tmpFile, mediaType, text, logLabel = '', on
|
|
|
1707
1755
|
|
|
1708
1756
|
const isWin = process.platform === 'win32';
|
|
1709
1757
|
const isMac = process.platform === 'darwin';
|
|
1710
|
-
try {
|
|
1758
|
+
try {
|
|
1711
1759
|
const stat = fs.statSync(tmpFile);
|
|
1712
1760
|
log(`Image ready: ${logLabel || path.basename(tmpFile)} (${stat.size} bytes)`);
|
|
1713
1761
|
|
|
@@ -1716,37 +1764,41 @@ function handlePreparedImageUpload({ tmpFile, mediaType, text, logLabel = '', on
|
|
|
1716
1764
|
execSync(`powershell -NoProfile -STA -Command "${psCmd}"`, { timeout: 10000 });
|
|
1717
1765
|
} else if (isMac) {
|
|
1718
1766
|
execSync(`osascript -e 'set the clipboard to (read POSIX file "${tmpFile}" as 芦class PNGf禄)'`, { timeout: 10000 });
|
|
1719
|
-
} else {
|
|
1720
|
-
const tool = startLinuxClipboardImage(tmpFile, mediaType);
|
|
1721
|
-
log(`Linux clipboard armed with ${tool}`);
|
|
1722
|
-
}
|
|
1767
|
+
} else {
|
|
1768
|
+
const tool = startLinuxClipboardImage(tmpFile, mediaType);
|
|
1769
|
+
log(`Linux clipboard armed with ${tool}`);
|
|
1770
|
+
}
|
|
1723
1771
|
log('Clipboard set with image');
|
|
1724
1772
|
|
|
1725
|
-
const pasteDelayMs = isWin || isMac ? 0 : 150;
|
|
1726
|
-
setTimeout(() => {
|
|
1727
|
-
if (!claudeProc) return;
|
|
1728
|
-
if (isWin) claudeProc.write('\x1bv');
|
|
1729
|
-
else claudeProc.write('\x16');
|
|
1730
|
-
log('Sent image paste keypress to PTY');
|
|
1731
|
-
}, pasteDelayMs);
|
|
1732
|
-
|
|
1733
|
-
setTimeout(() => {
|
|
1734
|
-
if (!claudeProc) return;
|
|
1735
|
-
const trimmedText = (text || '').trim();
|
|
1736
|
-
if (trimmedText) claudeProc.write(trimmedText);
|
|
1773
|
+
const pasteDelayMs = isWin || isMac ? 0 : 150;
|
|
1774
|
+
setTimeout(() => {
|
|
1775
|
+
if (!claudeProc) return;
|
|
1776
|
+
if (isWin) claudeProc.write('\x1bv');
|
|
1777
|
+
else claudeProc.write('\x16');
|
|
1778
|
+
log('Sent image paste keypress to PTY');
|
|
1779
|
+
}, pasteDelayMs);
|
|
1780
|
+
|
|
1781
|
+
setTimeout(() => {
|
|
1782
|
+
if (!claudeProc) return;
|
|
1783
|
+
const trimmedText = (text || '').trim();
|
|
1784
|
+
if (trimmedText) claudeProc.write(trimmedText);
|
|
1737
1785
|
|
|
1738
1786
|
setTimeout(() => {
|
|
1739
1787
|
if (claudeProc) claudeProc.write('\r');
|
|
1740
1788
|
log('Sent Enter after image paste' + (trimmedText ? ` + text: "${trimmedText.substring(0, 60)}"` : ''));
|
|
1741
1789
|
|
|
1790
|
+
if (!isWin && !isMac) {
|
|
1791
|
+
setTimeout(() => clearActiveLinuxClipboardProc('post-paste'), 1000);
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1742
1794
|
setTimeout(() => {
|
|
1743
1795
|
if (onCleanup) onCleanup();
|
|
1744
1796
|
else {
|
|
1745
1797
|
try { fs.unlinkSync(tmpFile); } catch {}
|
|
1746
1798
|
}
|
|
1747
1799
|
}, 5000);
|
|
1748
|
-
}, 150);
|
|
1749
|
-
}, 1000 + pasteDelayMs);
|
|
1800
|
+
}, 150);
|
|
1801
|
+
}, 1000 + pasteDelayMs);
|
|
1750
1802
|
} catch (err) {
|
|
1751
1803
|
log(`Image upload error: ${err.message}`);
|
|
1752
1804
|
if (onCleanup) onCleanup();
|
|
@@ -1757,32 +1809,32 @@ function handlePreparedImageUpload({ tmpFile, mediaType, text, logLabel = '', on
|
|
|
1757
1809
|
}
|
|
1758
1810
|
}
|
|
1759
1811
|
|
|
1760
|
-
function handleImageUpload(msg) {
|
|
1761
|
-
if (!claudeProc) {
|
|
1762
|
-
log('Image upload ignored: Claude not running');
|
|
1763
|
-
return;
|
|
1764
|
-
}
|
|
1765
|
-
if (!msg.base64) {
|
|
1766
|
-
log('Image upload ignored: no base64 data');
|
|
1767
|
-
return;
|
|
1768
|
-
}
|
|
1769
|
-
let tmpFile = null;
|
|
1770
|
-
|
|
1771
|
-
try {
|
|
1772
|
-
if (process.platform !== 'win32' && process.platform !== 'darwin') {
|
|
1773
|
-
const tool = assertLinuxClipboardAvailable();
|
|
1774
|
-
log(`Linux clipboard preflight OK (legacy upload): ${tool}`);
|
|
1775
|
-
}
|
|
1776
|
-
const buf = Buffer.from(msg.base64, 'base64');
|
|
1777
|
-
tmpFile = createTempImageFile(buf, msg.mediaType, `legacy_${Date.now()}`);
|
|
1778
|
-
log(`Image saved: ${tmpFile} (${buf.length} bytes)`);
|
|
1779
|
-
handlePreparedImageUpload({
|
|
1780
|
-
tmpFile,
|
|
1781
|
-
mediaType: msg.mediaType,
|
|
1782
|
-
text: msg.text || '',
|
|
1783
|
-
});
|
|
1784
|
-
setTurnState('running', { reason: 'legacy_image_upload' });
|
|
1785
|
-
} catch (err) {
|
|
1812
|
+
function handleImageUpload(msg) {
|
|
1813
|
+
if (!claudeProc) {
|
|
1814
|
+
log('Image upload ignored: Claude not running');
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
if (!msg.base64) {
|
|
1818
|
+
log('Image upload ignored: no base64 data');
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
let tmpFile = null;
|
|
1822
|
+
|
|
1823
|
+
try {
|
|
1824
|
+
if (process.platform !== 'win32' && process.platform !== 'darwin') {
|
|
1825
|
+
const tool = assertLinuxClipboardAvailable();
|
|
1826
|
+
log(`Linux clipboard preflight OK (legacy upload): ${tool}`);
|
|
1827
|
+
}
|
|
1828
|
+
const buf = Buffer.from(msg.base64, 'base64');
|
|
1829
|
+
tmpFile = createTempImageFile(buf, msg.mediaType, `legacy_${Date.now()}`);
|
|
1830
|
+
log(`Image saved: ${tmpFile} (${buf.length} bytes)`);
|
|
1831
|
+
handlePreparedImageUpload({
|
|
1832
|
+
tmpFile,
|
|
1833
|
+
mediaType: msg.mediaType,
|
|
1834
|
+
text: msg.text || '',
|
|
1835
|
+
});
|
|
1836
|
+
setTurnState('running', { reason: 'legacy_image_upload' });
|
|
1837
|
+
} catch (err) {
|
|
1786
1838
|
log(`Image upload error: ${err.message}`);
|
|
1787
1839
|
try { fs.unlinkSync(tmpFile); } catch {}
|
|
1788
1840
|
}
|