buddy-workbench 0.1.91 → 0.1.93

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buddy-workbench",
3
- "version": "0.1.91",
3
+ "version": "0.1.93",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -310,7 +310,9 @@ export function UserProfile({ userId }: { userId: string }) {
310
310
  router.get('/my-conflicts', async (_req, res) => {
311
311
  const host = configuredBitbucketHost();
312
312
  if (!host) return res.json({ values: [] });
313
- const dashboardUrl = `https://${host}/rest/api/latest/dashboard/pull-requests?role=author&state=ALL&limit=1000`;
313
+ // Bitbucket Server does not accept `state=ALL` for this endpoint; use the
314
+ // supported open state, matching the PR review/dashboard routes.
315
+ const dashboardUrl = `https://${host}/rest/api/latest/dashboard/pull-requests?role=author&state=OPEN&limit=100`;
314
316
  try {
315
317
  const response = await client.get(dashboardUrl, { headers: authHeaders() });
316
318
  if (response.status < 200 || response.status >= 300) return res.status(response.status).json({ error: `Unable to load authored Pull Requests (${response.status}).` });
@@ -15,15 +15,34 @@ const previewLimit = 2000;
15
15
  let lastValue = '';
16
16
  let lastImageHash = '';
17
17
  let suppressImageCaptureUntil = 0;
18
+ // Swift startup/permission failures can persist for the lifetime of the
19
+ // process (for example when the app has no GUI/clipboard access). Avoid
20
+ // recompiling Swift and incrementing the same error counter every poll.
21
+ let swiftClipboardRetryAt = 0;
18
22
  let mozjpegWasmModule = null;
23
+ const clipboardErrorCounts = new Map();
19
24
  const clipboardErrorLogTimes = new Map();
20
25
 
21
- function logClipboardError(stage, error) {
26
+ function markClipboardSuccess(...stages) {
27
+ for (const stage of stages) {
28
+ clipboardErrorCounts.delete(stage);
29
+ }
30
+ }
31
+
32
+ function logClipboardError(stage, error, threshold = 3) {
22
33
  // Ignore benign failures due to timeout or process termination (e.g., system sleep / screen lock / display off)
23
34
  if (error?.killed || error?.timedOut || error?.signal === 'SIGTERM' || error?.code === 'ETIMEDOUT') {
24
35
  return;
25
36
  }
26
37
 
38
+ const count = (clipboardErrorCounts.get(stage) || 0) + 1;
39
+ clipboardErrorCounts.set(stage, count);
40
+
41
+ // Transient filter: only log if failures happen consecutively past threshold
42
+ if (count < threshold) {
43
+ return;
44
+ }
45
+
27
46
  // Clipboard polling runs continuously. Avoid filling Error Log with the
28
47
  // same native-tool failure every two seconds while retaining diagnostics.
29
48
  const now = Date.now();
@@ -33,7 +52,7 @@ function logClipboardError(stage, error) {
33
52
  const details = String(error?.stderr || error?.stdout || error?.stack || error?.message || error || '').trim();
34
53
  addErrorRecord({
35
54
  source: 'Clipboard Monitor',
36
- message: `Failed during ${stage}.`,
55
+ message: `Failed during ${stage} (consecutive failures: ${count}).`,
37
56
  details: details || 'The native clipboard operation returned an unknown error.'
38
57
  });
39
58
  }
@@ -85,6 +104,7 @@ async function getMacClipboardImageData() {
85
104
  try {
86
105
  const { stdout } = await execFileAsync('osascript', ['-l', 'JavaScript', '-e', jsaScript], { timeout: 3000 });
87
106
  const str = stdout.trim();
107
+ markClipboardSuccess('macOS image clipboard read (osascript)');
88
108
  if (!str) return null;
89
109
  const firstColon = str.indexOf(':');
90
110
  const secondColon = str.indexOf(':', firstColon + 1);
@@ -96,11 +116,17 @@ async function getMacClipboardImageData() {
96
116
  return { width, height, rgbaBuf: Buffer.from(base64, 'base64') };
97
117
  } catch (error) {
98
118
  logClipboardError('macOS image clipboard read (osascript)', error);
119
+ // If osascript failed with an error, attempt the Swift fallback for image capture
120
+ const rawBuffer = await getMacClipboardImageBuffer();
121
+ if (rawBuffer && rawBuffer.length > 0) {
122
+ return { rawBuffer };
123
+ }
99
124
  return null;
100
125
  }
101
126
  }
102
127
 
103
128
  async function getMacClipboardImageBuffer() {
129
+ if (Date.now() < swiftClipboardRetryAt) return null;
104
130
  const swiftCode = `
105
131
  import Cocoa
106
132
  let pb = NSPasteboard.general
@@ -117,9 +143,19 @@ if let data = pb.data(forType: .png) {
117
143
  `;
118
144
  try {
119
145
  const { stdout } = await execFileAsync('swift', ['-e', swiftCode], { encoding: 'buffer', timeout: 3000 });
120
- if (stdout && stdout.length > 0) return stdout;
146
+ if (stdout && stdout.length > 0) {
147
+ markClipboardSuccess('macOS image clipboard read (Swift)');
148
+ swiftClipboardRetryAt = 0;
149
+ return stdout;
150
+ }
121
151
  } catch (error) {
152
+ // Back off before trying Swift again. A failed `swift -e` invocation is
153
+ // commonly persistent and clipboard polling runs every two seconds.
154
+ swiftClipboardRetryAt = Date.now() + 5 * 60 * 1000;
122
155
  logClipboardError('macOS image clipboard read (Swift)', error);
156
+ // This failure is now rate-limited by the retry window; don't let the
157
+ // displayed "consecutive failures" grow without bound.
158
+ clipboardErrorCounts.delete('macOS image clipboard read (Swift)');
123
159
  try {
124
160
  const jsaScript = `
125
161
  ObjC.import("AppKit");
@@ -141,7 +177,10 @@ if let data = pb.data(forType: .png) {
141
177
  `;
142
178
  const { stdout } = await execFileAsync('osascript', ['-l', 'JavaScript', '-e', jsaScript], { timeout: 3000 });
143
179
  const str = stdout.trim();
144
- if (str) return Buffer.from(str, 'base64');
180
+ if (str) {
181
+ markClipboardSuccess('macOS image clipboard fallback read (osascript)');
182
+ return Buffer.from(str, 'base64');
183
+ }
145
184
  } catch (fallbackError) {
146
185
  logClipboardError('macOS image clipboard fallback read (osascript)', fallbackError);
147
186
  }
@@ -334,7 +373,12 @@ export async function captureClipboard() {
334
373
  if (process.platform === 'darwin' && settings.clipboardImageEnabled !== false && Date.now() >= suppressImageCaptureUntil) {
335
374
  try {
336
375
  const imageData = await getMacClipboardImageData();
337
- if (imageData && imageData.rgbaBuf.length > 0) {
376
+ if (!imageData) {
377
+ // No image on clipboard (or transient capture failed and will retry next tick)
378
+ return;
379
+ }
380
+
381
+ if (imageData.rgbaBuf && imageData.rgbaBuf.length > 0) {
338
382
  const hash = createHash('md5').update(imageData.rgbaBuf).digest('hex');
339
383
  if (hash !== lastImageHash) {
340
384
  let isDuplicate = false;
@@ -356,12 +400,14 @@ export async function captureClipboard() {
356
400
  try {
357
401
  const mozjpegBuf = await compressMozjpeg(imageData.rgbaBuf, imageData.width, imageData.height, 75);
358
402
  saveClipboardImage(mozjpegBuf, 'jpg', hash);
403
+ markClipboardSuccess('image compression or save (WASM/JPEG)', 'image save');
359
404
  } catch (error) {
360
405
  logClipboardError('image compression or save (WASM/JPEG)', error);
361
406
  const rawBuf = await getMacClipboardImageBuffer();
362
407
  if (rawBuf) {
363
408
  try {
364
409
  saveClipboardImage(rawBuf, 'png', hash);
410
+ markClipboardSuccess('raw image save after compression fallback', 'image save');
365
411
  } catch (fallbackError) {
366
412
  logClipboardError('raw image save after compression fallback', fallbackError);
367
413
  }
@@ -369,32 +415,30 @@ export async function captureClipboard() {
369
415
  }
370
416
  }
371
417
  }
372
- } else {
373
- const imageBuffer = await getMacClipboardImageBuffer();
374
- if (imageBuffer && imageBuffer.length > 0) {
375
- const hash = createHash('md5').update(imageBuffer).digest('hex');
376
- if (hash !== lastImageHash) {
377
- let isDuplicate = false;
378
- const dates = clipboardDates();
379
- for (const d of dates) {
380
- const dayList = dayItems(d);
381
- if (dayList.some(item => {
382
- if (item.imageHash === hash || item.fileHash === hash) return true;
383
- if (Array.isArray(item.imageHashes) && item.imageHashes.includes(hash)) return true;
384
- return false;
385
- })) {
386
- isDuplicate = true;
387
- break;
388
- }
418
+ } else if (imageData.rawBuffer && imageData.rawBuffer.length > 0) {
419
+ const hash = createHash('md5').update(imageData.rawBuffer).digest('hex');
420
+ if (hash !== lastImageHash) {
421
+ let isDuplicate = false;
422
+ const dates = clipboardDates();
423
+ for (const d of dates) {
424
+ const dayList = dayItems(d);
425
+ if (dayList.some(item => {
426
+ if (item.imageHash === hash || item.fileHash === hash) return true;
427
+ if (Array.isArray(item.imageHashes) && item.imageHashes.includes(hash)) return true;
428
+ return false;
429
+ })) {
430
+ isDuplicate = true;
431
+ break;
389
432
  }
433
+ }
390
434
 
391
- lastImageHash = hash;
392
- if (!isDuplicate) {
393
- try {
394
- saveClipboardImage(imageBuffer, 'png', hash);
395
- } catch (error) {
396
- logClipboardError('image save', error);
397
- }
435
+ lastImageHash = hash;
436
+ if (!isDuplicate) {
437
+ try {
438
+ saveClipboardImage(imageData.rawBuffer, 'png', hash);
439
+ markClipboardSuccess('image save');
440
+ } catch (error) {
441
+ logClipboardError('image save', error);
398
442
  }
399
443
  }
400
444
  }