claude-remote 0.4.5 → 0.4.7
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 +253 -161
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -185,7 +185,8 @@ let tailRemainder = Buffer.alloc(0);
|
|
|
185
185
|
let tailCatchingUp = false; // true while reading historical transcript content
|
|
186
186
|
const isTTY = process.stdin.isTTY && process.stdout.isTTY;
|
|
187
187
|
const LEGACY_REPLAY_DELAY_MS = 1500;
|
|
188
|
-
const IMAGE_UPLOAD_TTL_MS = 15 * 60 * 1000;
|
|
188
|
+
const IMAGE_UPLOAD_TTL_MS = 15 * 60 * 1000;
|
|
189
|
+
const LINUX_CLIPBOARD_READY_GRACE_MS = 400;
|
|
189
190
|
const IMAGE_UPLOAD_DIR = path.join(CLAUDE_HOME, 'remote-uploads');
|
|
190
191
|
let turnStateVersion = 0;
|
|
191
192
|
let turnState = {
|
|
@@ -194,10 +195,11 @@ let turnState = {
|
|
|
194
195
|
version: 0,
|
|
195
196
|
updatedAt: Date.now(),
|
|
196
197
|
};
|
|
197
|
-
let ttyInputForwarderAttached = false;
|
|
198
|
-
let ttyInputHandler = null;
|
|
199
|
-
let ttyResizeHandler = null;
|
|
200
|
-
let activeLinuxClipboardProc = null;
|
|
198
|
+
let ttyInputForwarderAttached = false;
|
|
199
|
+
let ttyInputHandler = null;
|
|
200
|
+
let ttyResizeHandler = null;
|
|
201
|
+
let activeLinuxClipboardProc = null;
|
|
202
|
+
let linuxImagePasteInFlight = false;
|
|
201
203
|
|
|
202
204
|
// --- Permission approval state ---
|
|
203
205
|
let approvalSeq = 0;
|
|
@@ -220,10 +222,10 @@ function formatTtyInputChunk(chunk) {
|
|
|
220
222
|
return `len=${buf.length} hex=${buf.toString('hex')} base64=${buf.toString('base64')} utf8=${JSON.stringify(buf.toString('utf8'))}`;
|
|
221
223
|
}
|
|
222
224
|
|
|
223
|
-
function clearActiveLinuxClipboardProc(reason = '') {
|
|
224
|
-
if (!activeLinuxClipboardProc) return;
|
|
225
|
-
const { child, tool } = activeLinuxClipboardProc;
|
|
226
|
-
activeLinuxClipboardProc = null;
|
|
225
|
+
function clearActiveLinuxClipboardProc(reason = '') {
|
|
226
|
+
if (!activeLinuxClipboardProc) return;
|
|
227
|
+
const { child, tool } = activeLinuxClipboardProc;
|
|
228
|
+
activeLinuxClipboardProc = null;
|
|
227
229
|
try {
|
|
228
230
|
child.kill('SIGTERM');
|
|
229
231
|
log(`Linux clipboard process terminated (${tool}) reason=${reason || 'cleanup'}`);
|
|
@@ -694,67 +696,131 @@ function createTempImageFile(buffer, mediaType, uploadId) {
|
|
|
694
696
|
return tmpFile;
|
|
695
697
|
}
|
|
696
698
|
|
|
697
|
-
function
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
699
|
+
function isLinuxClipboardToolInstalled(tool) {
|
|
700
|
+
try {
|
|
701
|
+
execSync(`command -v ${tool} >/dev/null 2>&1`, {
|
|
702
|
+
stdio: 'ignore',
|
|
703
|
+
shell: '/bin/sh',
|
|
704
|
+
timeout: 2000,
|
|
705
|
+
});
|
|
706
|
+
return true;
|
|
707
|
+
} catch {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function setLinuxImagePasteInFlight(active, reason = '') {
|
|
713
|
+
linuxImagePasteInFlight = !!active;
|
|
714
|
+
if (reason) log(`Linux image paste lock=${linuxImagePasteInFlight ? 'on' : 'off'} reason=${reason}`);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function getLinuxClipboardToolCandidates() {
|
|
718
|
+
if (process.platform === 'win32' || process.platform === 'darwin') return [];
|
|
719
|
+
const preferred = [];
|
|
720
|
+
if (process.env.WAYLAND_DISPLAY) preferred.push('wl-copy');
|
|
721
|
+
if (process.env.DISPLAY) preferred.push('xclip');
|
|
722
|
+
return preferred;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function assertLinuxClipboardAvailable() {
|
|
726
|
+
const candidates = getLinuxClipboardToolCandidates();
|
|
727
|
+
const available = candidates.filter(isLinuxClipboardToolInstalled);
|
|
728
|
+
if (available.length > 0) return available;
|
|
729
|
+
if (!process.env.WAYLAND_DISPLAY && !process.env.DISPLAY) {
|
|
730
|
+
throw new Error('Linux image paste requires an active graphical session (WAYLAND_DISPLAY or DISPLAY).');
|
|
731
|
+
}
|
|
732
|
+
throw new Error('Linux image paste requires wl-copy or xclip on the server. Install a matching clipboard tool and try again.');
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function spawnLinuxClipboardTool(tool, imageBuffer, type) {
|
|
736
|
+
return new Promise((resolve, reject) => {
|
|
737
|
+
const args = tool === 'xclip'
|
|
738
|
+
? ['-quiet', '-selection', 'clipboard', '-t', type, '-i']
|
|
739
|
+
: ['--type', type];
|
|
740
|
+
const child = spawn(tool, args, {
|
|
741
|
+
detached: true,
|
|
742
|
+
stdio: ['pipe', 'ignore', 'pipe'],
|
|
743
|
+
});
|
|
744
|
+
let settled = false;
|
|
745
|
+
let stderr = '';
|
|
746
|
+
let readyTimer = null;
|
|
747
|
+
|
|
748
|
+
const settleFailure = (message) => {
|
|
749
|
+
if (settled) return;
|
|
750
|
+
settled = true;
|
|
751
|
+
if (readyTimer) clearTimeout(readyTimer);
|
|
752
|
+
if (child.exitCode == null && child.signalCode == null) {
|
|
753
|
+
try { child.kill('SIGTERM'); } catch {}
|
|
754
|
+
}
|
|
755
|
+
reject(new Error(message));
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
const settleSuccess = (trackProcess = true) => {
|
|
759
|
+
if (settled) return;
|
|
760
|
+
settled = true;
|
|
761
|
+
if (readyTimer) clearTimeout(readyTimer);
|
|
762
|
+
if (trackProcess && child.exitCode == null && child.signalCode == null) {
|
|
763
|
+
activeLinuxClipboardProc = { child, tool };
|
|
764
|
+
child.unref();
|
|
765
|
+
}
|
|
766
|
+
resolve(tool);
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
child.on('error', (err) => {
|
|
770
|
+
log(`Linux clipboard process error (${tool}): ${err.message}`);
|
|
771
|
+
settleFailure(`Linux clipboard tool ${tool} failed: ${err.message}`);
|
|
772
|
+
});
|
|
773
|
+
child.stderr.on('data', (chunk) => {
|
|
774
|
+
stderr += chunk.toString('utf8');
|
|
775
|
+
if (stderr.length > 2000) stderr = stderr.slice(-2000);
|
|
776
|
+
});
|
|
777
|
+
child.on('exit', (code, signal) => {
|
|
778
|
+
if (activeLinuxClipboardProc && activeLinuxClipboardProc.child === child) activeLinuxClipboardProc = null;
|
|
779
|
+
const extra = stderr.trim() ? ` stderr=${JSON.stringify(stderr.trim())}` : '';
|
|
780
|
+
log(`Linux clipboard process exited (${tool}) code=${code ?? 'null'} signal=${signal ?? 'null'}${extra}`);
|
|
781
|
+
if (!settled) {
|
|
782
|
+
if (tool === 'xclip' && code === 0 && !signal && !stderr.trim()) {
|
|
783
|
+
log('Linux clipboard xclip exited cleanly without stderr; treating clipboard arm as successful');
|
|
784
|
+
settleSuccess(false);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const detail = stderr.trim() || `exit code ${code ?? 'null'} signal ${signal ?? 'null'}`;
|
|
788
|
+
settleFailure(`Linux clipboard tool ${tool} exited before paste: ${detail}`);
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
child.stdin.on('error', (err) => {
|
|
792
|
+
if (err.code === 'EPIPE') {
|
|
793
|
+
settleFailure(`Linux clipboard tool ${tool} closed its input early`);
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
log(`Linux clipboard stdin error (${tool}): ${err.message}`);
|
|
797
|
+
settleFailure(`Linux clipboard tool ${tool} stdin failed: ${err.message}`);
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
child.stdin.end(imageBuffer);
|
|
801
|
+
log(`Linux clipboard process started (${tool}) pid=${child.pid ?? 'null'} type=${type} bytes=${imageBuffer.length}`);
|
|
802
|
+
readyTimer = setTimeout(() => settleSuccess(), LINUX_CLIPBOARD_READY_GRACE_MS);
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
async function startLinuxClipboardImage(tmpFile, mediaType) {
|
|
807
|
+
const type = String(mediaType || 'image/png').toLowerCase();
|
|
808
|
+
const imageBuffer = fs.readFileSync(tmpFile);
|
|
809
|
+
const availableTools = assertLinuxClipboardAvailable();
|
|
810
|
+
clearActiveLinuxClipboardProc('replace');
|
|
811
|
+
|
|
812
|
+
let lastErr = null;
|
|
813
|
+
for (const tool of availableTools) {
|
|
814
|
+
try {
|
|
815
|
+
return await spawnLinuxClipboardTool(tool, imageBuffer, type);
|
|
816
|
+
} catch (err) {
|
|
817
|
+
lastErr = err;
|
|
818
|
+
log(`Linux clipboard arm failed (${tool}): ${err.message}`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
throw lastErr || new Error('Linux clipboard could not be initialized');
|
|
823
|
+
}
|
|
758
824
|
|
|
759
825
|
setInterval(() => {
|
|
760
826
|
const now = Date.now();
|
|
@@ -1004,16 +1070,16 @@ wss.on('connection', (ws, req) => {
|
|
|
1004
1070
|
sendUploadStatus(ws, '', 'error', { message: 'Missing uploadId' });
|
|
1005
1071
|
break;
|
|
1006
1072
|
}
|
|
1007
|
-
cleanupImageUpload(uploadId);
|
|
1008
|
-
if (process.platform !== 'win32' && process.platform !== 'darwin') {
|
|
1009
|
-
try {
|
|
1010
|
-
const
|
|
1011
|
-
log(`Linux clipboard preflight OK: ${
|
|
1012
|
-
} catch (err) {
|
|
1013
|
-
sendUploadStatus(ws, uploadId, 'error', { message: err.message });
|
|
1014
|
-
break;
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1073
|
+
cleanupImageUpload(uploadId);
|
|
1074
|
+
if (process.platform !== 'win32' && process.platform !== 'darwin') {
|
|
1075
|
+
try {
|
|
1076
|
+
const tools = assertLinuxClipboardAvailable();
|
|
1077
|
+
log(`Linux clipboard preflight OK: ${tools.join(', ')}`);
|
|
1078
|
+
} catch (err) {
|
|
1079
|
+
sendUploadStatus(ws, uploadId, 'error', { message: err.message });
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1017
1083
|
pendingImageUploads.set(uploadId, {
|
|
1018
1084
|
id: uploadId,
|
|
1019
1085
|
owner: ws,
|
|
@@ -1115,13 +1181,13 @@ wss.on('connection', (ws, req) => {
|
|
|
1115
1181
|
sendUploadStatus(ws, uploadId, 'error', { message: 'Upload not ready' });
|
|
1116
1182
|
break;
|
|
1117
1183
|
}
|
|
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),
|
|
1184
|
+
try {
|
|
1185
|
+
await handlePreparedImageUpload({
|
|
1186
|
+
tmpFile: upload.tmpFile,
|
|
1187
|
+
mediaType: upload.mediaType,
|
|
1188
|
+
text: msg.text || '',
|
|
1189
|
+
logLabel: upload.name || uploadId,
|
|
1190
|
+
onCleanup: () => cleanupImageUpload(uploadId),
|
|
1125
1191
|
});
|
|
1126
1192
|
upload.submitted = true;
|
|
1127
1193
|
upload.updatedAt = Date.now();
|
|
@@ -1749,62 +1815,85 @@ function restartClaude(newCwd) {
|
|
|
1749
1815
|
// ============================================================
|
|
1750
1816
|
// 5. Image Upload → Clipboard Injection
|
|
1751
1817
|
// ============================================================
|
|
1752
|
-
function handlePreparedImageUpload({ tmpFile, mediaType, text, logLabel = '', onCleanup = null }) {
|
|
1753
|
-
if (!claudeProc) throw new Error('Claude not running');
|
|
1754
|
-
if (!tmpFile || !fs.existsSync(tmpFile)) throw new Error('Prepared image file missing');
|
|
1755
|
-
|
|
1756
|
-
const isWin = process.platform === 'win32';
|
|
1757
|
-
const isMac = process.platform === 'darwin';
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1818
|
+
async function handlePreparedImageUpload({ tmpFile, mediaType, text, logLabel = '', onCleanup = null }) {
|
|
1819
|
+
if (!claudeProc) throw new Error('Claude not running');
|
|
1820
|
+
if (!tmpFile || !fs.existsSync(tmpFile)) throw new Error('Prepared image file missing');
|
|
1821
|
+
|
|
1822
|
+
const isWin = process.platform === 'win32';
|
|
1823
|
+
const isMac = process.platform === 'darwin';
|
|
1824
|
+
const isLinux = !isWin && !isMac;
|
|
1825
|
+
if (isLinux && linuxImagePasteInFlight) {
|
|
1826
|
+
throw new Error('Another Linux image paste is still in progress. Please wait a moment and try again.');
|
|
1827
|
+
}
|
|
1828
|
+
try {
|
|
1829
|
+
const stat = fs.statSync(tmpFile);
|
|
1830
|
+
log(`Image ready: ${logLabel || path.basename(tmpFile)} (${stat.size} bytes)`);
|
|
1831
|
+
if (isLinux) setLinuxImagePasteInFlight(true, 'prepare_upload');
|
|
1832
|
+
|
|
1833
|
+
if (isWin) {
|
|
1834
|
+
const psCmd = `Add-Type -AssemblyName System.Drawing; Add-Type -AssemblyName System.Windows.Forms; $img = [System.Drawing.Image]::FromFile('${tmpFile.replace(/'/g, "''")}'); [System.Windows.Forms.Clipboard]::SetImage($img); $img.Dispose()`;
|
|
1835
|
+
execSync(`powershell -NoProfile -STA -Command "${psCmd}"`, { timeout: 10000 });
|
|
1836
|
+
} else if (isMac) {
|
|
1766
1837
|
execSync(`osascript -e 'set the clipboard to (read POSIX file "${tmpFile}" as 芦class PNGf禄)'`, { timeout: 10000 });
|
|
1767
|
-
} else {
|
|
1768
|
-
const tool = startLinuxClipboardImage(tmpFile, mediaType);
|
|
1769
|
-
log(`Linux clipboard armed with ${tool}`);
|
|
1770
|
-
}
|
|
1771
|
-
log('Clipboard set with image');
|
|
1772
|
-
|
|
1773
|
-
const pasteDelayMs = isWin || isMac ? 0 : 150;
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1838
|
+
} else {
|
|
1839
|
+
const tool = await startLinuxClipboardImage(tmpFile, mediaType);
|
|
1840
|
+
log(`Linux clipboard armed with ${tool}`);
|
|
1841
|
+
}
|
|
1842
|
+
log('Clipboard set with image');
|
|
1843
|
+
|
|
1844
|
+
const pasteDelayMs = isWin || isMac ? 0 : 150;
|
|
1845
|
+
await new Promise((resolve, reject) => {
|
|
1846
|
+
setTimeout(() => {
|
|
1847
|
+
if (!claudeProc) {
|
|
1848
|
+
reject(new Error('Claude stopped before image paste'));
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
if (isWin) claudeProc.write('\x1bv');
|
|
1852
|
+
else claudeProc.write('\x16');
|
|
1853
|
+
log('Sent image paste keypress to PTY');
|
|
1854
|
+
|
|
1855
|
+
setTimeout(() => {
|
|
1856
|
+
if (!claudeProc) {
|
|
1857
|
+
reject(new Error('Claude stopped before image prompt'));
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
const trimmedText = (text || '').trim();
|
|
1861
|
+
if (trimmedText) claudeProc.write(trimmedText);
|
|
1862
|
+
|
|
1863
|
+
setTimeout(() => {
|
|
1864
|
+
if (!claudeProc) {
|
|
1865
|
+
reject(new Error('Claude stopped before image submit'));
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
claudeProc.write('\r');
|
|
1869
|
+
log('Sent Enter after image paste' + (trimmedText ? ` + text: "${trimmedText.substring(0, 60)}"` : ''));
|
|
1870
|
+
|
|
1871
|
+
if (isLinux) {
|
|
1872
|
+
setTimeout(() => clearActiveLinuxClipboardProc('post-paste'), 1000);
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
setTimeout(() => {
|
|
1876
|
+
if (isLinux) setLinuxImagePasteInFlight(false, 'cleanup');
|
|
1877
|
+
if (onCleanup) onCleanup();
|
|
1878
|
+
else {
|
|
1879
|
+
try { fs.unlinkSync(tmpFile); } catch {}
|
|
1880
|
+
}
|
|
1881
|
+
}, 5000);
|
|
1882
|
+
resolve();
|
|
1883
|
+
}, 150);
|
|
1884
|
+
}, 1000);
|
|
1885
|
+
}, pasteDelayMs);
|
|
1886
|
+
});
|
|
1887
|
+
} catch (err) {
|
|
1888
|
+
log(`Image upload error: ${err.message}`);
|
|
1889
|
+
if (isLinux) {
|
|
1890
|
+
clearActiveLinuxClipboardProc('error');
|
|
1891
|
+
setLinuxImagePasteInFlight(false, 'error');
|
|
1892
|
+
}
|
|
1893
|
+
if (onCleanup) onCleanup();
|
|
1894
|
+
else {
|
|
1895
|
+
try { fs.unlinkSync(tmpFile); } catch {}
|
|
1896
|
+
}
|
|
1808
1897
|
throw err;
|
|
1809
1898
|
}
|
|
1810
1899
|
}
|
|
@@ -1820,25 +1909,28 @@ function handleImageUpload(msg) {
|
|
|
1820
1909
|
}
|
|
1821
1910
|
let tmpFile = null;
|
|
1822
1911
|
|
|
1823
|
-
try {
|
|
1824
|
-
if (process.platform !== 'win32' && process.platform !== 'darwin') {
|
|
1825
|
-
const
|
|
1826
|
-
log(`Linux clipboard preflight OK (legacy upload): ${
|
|
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
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
}
|
|
1841
|
-
}
|
|
1912
|
+
try {
|
|
1913
|
+
if (process.platform !== 'win32' && process.platform !== 'darwin') {
|
|
1914
|
+
const tools = assertLinuxClipboardAvailable();
|
|
1915
|
+
log(`Linux clipboard preflight OK (legacy upload): ${tools.join(', ')}`);
|
|
1916
|
+
}
|
|
1917
|
+
const buf = Buffer.from(msg.base64, 'base64');
|
|
1918
|
+
tmpFile = createTempImageFile(buf, msg.mediaType, `legacy_${Date.now()}`);
|
|
1919
|
+
log(`Image saved: ${tmpFile} (${buf.length} bytes)`);
|
|
1920
|
+
handlePreparedImageUpload({
|
|
1921
|
+
tmpFile,
|
|
1922
|
+
mediaType: msg.mediaType,
|
|
1923
|
+
text: msg.text || '',
|
|
1924
|
+
}).then(() => {
|
|
1925
|
+
setTurnState('running', { reason: 'legacy_image_upload' });
|
|
1926
|
+
}).catch((err) => {
|
|
1927
|
+
log(`Image upload error: ${err.message}`);
|
|
1928
|
+
});
|
|
1929
|
+
} catch (err) {
|
|
1930
|
+
log(`Image upload error: ${err.message}`);
|
|
1931
|
+
try { fs.unlinkSync(tmpFile); } catch {}
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1842
1934
|
|
|
1843
1935
|
// ============================================================
|
|
1844
1936
|
// 6. Hook Auto-Setup
|