buddy-workbench 0.1.90 → 0.1.92
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/services/clipboard-history.js +66 -29
- package/ui/dist/assets/{index-C_mwJrnS.js → index-CJCnv16h.js} +5 -5
- package/ui/dist/assets/{index-BPVyCEqp.css → index-CMDqLDOG.css} +1 -1
- package/ui/dist/assets/{xmindAdapter-BhBneciA.js → xmindAdapter-BHiTM67L.js} +1 -1
- package/ui/dist/index.html +2 -2
- package/ui/dist/sw.js +2 -2
package/package.json
CHANGED
|
@@ -16,9 +16,29 @@ let lastValue = '';
|
|
|
16
16
|
let lastImageHash = '';
|
|
17
17
|
let suppressImageCaptureUntil = 0;
|
|
18
18
|
let mozjpegWasmModule = null;
|
|
19
|
+
const clipboardErrorCounts = new Map();
|
|
19
20
|
const clipboardErrorLogTimes = new Map();
|
|
20
21
|
|
|
21
|
-
function
|
|
22
|
+
function markClipboardSuccess(...stages) {
|
|
23
|
+
for (const stage of stages) {
|
|
24
|
+
clipboardErrorCounts.delete(stage);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function logClipboardError(stage, error, threshold = 3) {
|
|
29
|
+
// Ignore benign failures due to timeout or process termination (e.g., system sleep / screen lock / display off)
|
|
30
|
+
if (error?.killed || error?.timedOut || error?.signal === 'SIGTERM' || error?.code === 'ETIMEDOUT') {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const count = (clipboardErrorCounts.get(stage) || 0) + 1;
|
|
35
|
+
clipboardErrorCounts.set(stage, count);
|
|
36
|
+
|
|
37
|
+
// Transient filter: only log if failures happen consecutively past threshold
|
|
38
|
+
if (count < threshold) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
22
42
|
// Clipboard polling runs continuously. Avoid filling Error Log with the
|
|
23
43
|
// same native-tool failure every two seconds while retaining diagnostics.
|
|
24
44
|
const now = Date.now();
|
|
@@ -28,7 +48,7 @@ function logClipboardError(stage, error) {
|
|
|
28
48
|
const details = String(error?.stderr || error?.stdout || error?.stack || error?.message || error || '').trim();
|
|
29
49
|
addErrorRecord({
|
|
30
50
|
source: 'Clipboard Monitor',
|
|
31
|
-
message: `Failed during ${stage}.`,
|
|
51
|
+
message: `Failed during ${stage} (consecutive failures: ${count}).`,
|
|
32
52
|
details: details || 'The native clipboard operation returned an unknown error.'
|
|
33
53
|
});
|
|
34
54
|
}
|
|
@@ -80,6 +100,7 @@ async function getMacClipboardImageData() {
|
|
|
80
100
|
try {
|
|
81
101
|
const { stdout } = await execFileAsync('osascript', ['-l', 'JavaScript', '-e', jsaScript], { timeout: 3000 });
|
|
82
102
|
const str = stdout.trim();
|
|
103
|
+
markClipboardSuccess('macOS image clipboard read (osascript)');
|
|
83
104
|
if (!str) return null;
|
|
84
105
|
const firstColon = str.indexOf(':');
|
|
85
106
|
const secondColon = str.indexOf(':', firstColon + 1);
|
|
@@ -91,6 +112,11 @@ async function getMacClipboardImageData() {
|
|
|
91
112
|
return { width, height, rgbaBuf: Buffer.from(base64, 'base64') };
|
|
92
113
|
} catch (error) {
|
|
93
114
|
logClipboardError('macOS image clipboard read (osascript)', error);
|
|
115
|
+
// If osascript failed with an error, attempt the Swift fallback for image capture
|
|
116
|
+
const rawBuffer = await getMacClipboardImageBuffer();
|
|
117
|
+
if (rawBuffer && rawBuffer.length > 0) {
|
|
118
|
+
return { rawBuffer };
|
|
119
|
+
}
|
|
94
120
|
return null;
|
|
95
121
|
}
|
|
96
122
|
}
|
|
@@ -112,7 +138,10 @@ if let data = pb.data(forType: .png) {
|
|
|
112
138
|
`;
|
|
113
139
|
try {
|
|
114
140
|
const { stdout } = await execFileAsync('swift', ['-e', swiftCode], { encoding: 'buffer', timeout: 3000 });
|
|
115
|
-
if (stdout && stdout.length > 0)
|
|
141
|
+
if (stdout && stdout.length > 0) {
|
|
142
|
+
markClipboardSuccess('macOS image clipboard read (Swift)');
|
|
143
|
+
return stdout;
|
|
144
|
+
}
|
|
116
145
|
} catch (error) {
|
|
117
146
|
logClipboardError('macOS image clipboard read (Swift)', error);
|
|
118
147
|
try {
|
|
@@ -136,7 +165,10 @@ if let data = pb.data(forType: .png) {
|
|
|
136
165
|
`;
|
|
137
166
|
const { stdout } = await execFileAsync('osascript', ['-l', 'JavaScript', '-e', jsaScript], { timeout: 3000 });
|
|
138
167
|
const str = stdout.trim();
|
|
139
|
-
if (str)
|
|
168
|
+
if (str) {
|
|
169
|
+
markClipboardSuccess('macOS image clipboard fallback read (osascript)');
|
|
170
|
+
return Buffer.from(str, 'base64');
|
|
171
|
+
}
|
|
140
172
|
} catch (fallbackError) {
|
|
141
173
|
logClipboardError('macOS image clipboard fallback read (osascript)', fallbackError);
|
|
142
174
|
}
|
|
@@ -329,7 +361,12 @@ export async function captureClipboard() {
|
|
|
329
361
|
if (process.platform === 'darwin' && settings.clipboardImageEnabled !== false && Date.now() >= suppressImageCaptureUntil) {
|
|
330
362
|
try {
|
|
331
363
|
const imageData = await getMacClipboardImageData();
|
|
332
|
-
if (imageData
|
|
364
|
+
if (!imageData) {
|
|
365
|
+
// No image on clipboard (or transient capture failed and will retry next tick)
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (imageData.rgbaBuf && imageData.rgbaBuf.length > 0) {
|
|
333
370
|
const hash = createHash('md5').update(imageData.rgbaBuf).digest('hex');
|
|
334
371
|
if (hash !== lastImageHash) {
|
|
335
372
|
let isDuplicate = false;
|
|
@@ -351,12 +388,14 @@ export async function captureClipboard() {
|
|
|
351
388
|
try {
|
|
352
389
|
const mozjpegBuf = await compressMozjpeg(imageData.rgbaBuf, imageData.width, imageData.height, 75);
|
|
353
390
|
saveClipboardImage(mozjpegBuf, 'jpg', hash);
|
|
391
|
+
markClipboardSuccess('image compression or save (WASM/JPEG)', 'image save');
|
|
354
392
|
} catch (error) {
|
|
355
393
|
logClipboardError('image compression or save (WASM/JPEG)', error);
|
|
356
394
|
const rawBuf = await getMacClipboardImageBuffer();
|
|
357
395
|
if (rawBuf) {
|
|
358
396
|
try {
|
|
359
397
|
saveClipboardImage(rawBuf, 'png', hash);
|
|
398
|
+
markClipboardSuccess('raw image save after compression fallback', 'image save');
|
|
360
399
|
} catch (fallbackError) {
|
|
361
400
|
logClipboardError('raw image save after compression fallback', fallbackError);
|
|
362
401
|
}
|
|
@@ -364,32 +403,30 @@ export async function captureClipboard() {
|
|
|
364
403
|
}
|
|
365
404
|
}
|
|
366
405
|
}
|
|
367
|
-
} else {
|
|
368
|
-
const
|
|
369
|
-
if (
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
isDuplicate = true;
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
406
|
+
} else if (imageData.rawBuffer && imageData.rawBuffer.length > 0) {
|
|
407
|
+
const hash = createHash('md5').update(imageData.rawBuffer).digest('hex');
|
|
408
|
+
if (hash !== lastImageHash) {
|
|
409
|
+
let isDuplicate = false;
|
|
410
|
+
const dates = clipboardDates();
|
|
411
|
+
for (const d of dates) {
|
|
412
|
+
const dayList = dayItems(d);
|
|
413
|
+
if (dayList.some(item => {
|
|
414
|
+
if (item.imageHash === hash || item.fileHash === hash) return true;
|
|
415
|
+
if (Array.isArray(item.imageHashes) && item.imageHashes.includes(hash)) return true;
|
|
416
|
+
return false;
|
|
417
|
+
})) {
|
|
418
|
+
isDuplicate = true;
|
|
419
|
+
break;
|
|
384
420
|
}
|
|
421
|
+
}
|
|
385
422
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
423
|
+
lastImageHash = hash;
|
|
424
|
+
if (!isDuplicate) {
|
|
425
|
+
try {
|
|
426
|
+
saveClipboardImage(imageData.rawBuffer, 'png', hash);
|
|
427
|
+
markClipboardSuccess('image save');
|
|
428
|
+
} catch (error) {
|
|
429
|
+
logClipboardError('image save', error);
|
|
393
430
|
}
|
|
394
431
|
}
|
|
395
432
|
}
|