blun-king-cli 9.1.293 → 9.1.295

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.
@@ -11,4 +11,33 @@ async function waitForAbortableCompletion(signal, work, onAbort) {
11
11
  }
12
12
  }
13
13
 
14
- module.exports = { waitForAbortableCompletion };
14
+ function createAbortOutcome(signal, outcome, isRelevant = () => true) {
15
+ let listener;
16
+ let listening = false;
17
+ const promise = new Promise((resolve) => {
18
+ if (signal === undefined) return;
19
+ const resolveIfRelevant = () => {
20
+ if (isRelevant()) resolve(outcome());
21
+ };
22
+ if (signal.aborted) {
23
+ resolveIfRelevant();
24
+ return;
25
+ }
26
+ listener = resolveIfRelevant;
27
+ listening = true;
28
+ signal.addEventListener('abort', listener, { once: true });
29
+ });
30
+ return {
31
+ promise,
32
+ cleanup() {
33
+ if (!listening) return;
34
+ listening = false;
35
+ signal.removeEventListener('abort', listener);
36
+ },
37
+ };
38
+ }
39
+
40
+ module.exports = {
41
+ createAbortOutcome,
42
+ waitForAbortableCompletion,
43
+ };
@@ -22,6 +22,7 @@ const INSTALL_REGISTRY_URL = 'https://registry.npmjs.org/';
22
22
  const FALLBACK_MANIFEST_URL = 'https://chat.blun.ai/blun-code-version.json';
23
23
  const SNOOZE_MS = 7 * 24 * 60 * 60 * 1000;
24
24
  const REQUEST_TIMEOUT_MS = 2_500;
25
+ const UPDATE_SOURCE_RETRY_DELAYS_MS = Object.freeze([250, 750]);
25
26
  const STATE_FILE = 'update-notice.json';
26
27
  const UPDATE_SKIP_FILE = 'update-skip-version.json';
27
28
  const PENDING_UPDATE_FILE = 'update-pending.json';
@@ -385,10 +386,54 @@ function requestTrustedJson(url, options = {}) {
385
386
  });
386
387
  }
387
388
 
388
- async function loadReleaseSources() {
389
+ function retryableUpdateSourceError(error) {
390
+ const message = error instanceof Error ? error.message : String(error || '');
391
+ if (message === 'UPDATE_REQUEST_TIMEOUT') return true;
392
+ const statusMatch = /^UPDATE_HTTP_STATUS_(\d{3})$/u.exec(message);
393
+ if (statusMatch) {
394
+ const status = Number(statusMatch[1]);
395
+ return status === 408 || status === 425 || status === 429 || status >= 500;
396
+ }
397
+ return new Set([
398
+ 'EAI_AGAIN',
399
+ 'ECONNREFUSED',
400
+ 'ECONNRESET',
401
+ 'ENETDOWN',
402
+ 'ENETUNREACH',
403
+ 'ENOTFOUND',
404
+ 'EPIPE',
405
+ 'ETIMEDOUT',
406
+ ]).has(error?.code);
407
+ }
408
+
409
+ function sleepForUpdateRetry(delayMs) {
410
+ return new Promise((resolve) => setTimeout(resolve, delayMs));
411
+ }
412
+
413
+ async function requestTrustedJsonWithRetry(url, options = {}) {
414
+ const request = options.request || requestTrustedJson;
415
+ const sleep = options.sleep || sleepForUpdateRetry;
416
+ const configuredDelays = Array.isArray(options.retryDelaysMs)
417
+ ? options.retryDelaysMs
418
+ : UPDATE_SOURCE_RETRY_DELAYS_MS;
419
+ const retryDelaysMs = configuredDelays
420
+ .slice(0, UPDATE_SOURCE_RETRY_DELAYS_MS.length)
421
+ .map((value) => (Number.isFinite(value) && value >= 0 ? Math.min(value, 2_000) : 0));
422
+
423
+ for (let attempt = 0; ; attempt += 1) {
424
+ try {
425
+ return await request(url, options);
426
+ } catch (error) {
427
+ if (attempt >= retryDelaysMs.length || !retryableUpdateSourceError(error)) throw error;
428
+ await sleep(retryDelaysMs[attempt]);
429
+ }
430
+ }
431
+ }
432
+
433
+ async function loadReleaseSources(options = {}) {
389
434
  const [registry, fallback] = await Promise.allSettled([
390
- requestTrustedJson(REGISTRY_URL),
391
- requestTrustedJson(FALLBACK_MANIFEST_URL),
435
+ requestTrustedJsonWithRetry(REGISTRY_URL, options),
436
+ requestTrustedJsonWithRetry(FALLBACK_MANIFEST_URL, options),
392
437
  ]);
393
438
  return {
394
439
  registryDocument: registry.status === 'fulfilled' ? registry.value : undefined,
@@ -1636,6 +1681,7 @@ module.exports = {
1636
1681
  readSkippedVersion,
1637
1682
  readSnoozeState,
1638
1683
  requestTrustedJson,
1684
+ requestTrustedJsonWithRetry,
1639
1685
  resolveEffectiveRelease,
1640
1686
  runExplicitUpdate,
1641
1687
  runUpdateNotice,
package/blun.mjs CHANGED
@@ -29400,7 +29400,7 @@ function buildBackgroundTaskNotificationBody(info) {
29400
29400
  "The subagent retains its full prior context across the restart, but any in-flight tool call lost its result and may need to be redone."
29401
29401
  ].join("\n")}`;
29402
29402
  }
29403
- var MAX_OUTPUT_BYTES$2, NOTIFICATION_FALLBACK_PREVIEW_BYTES, MAX_FOREGROUND_CAPTURE_BYTES, captureForegroundOutputChunk, SIGTERM_GRACE_MS$1, USER_INTERRUPT_REASON$1, _ALPHABET, BackgroundManager;
29403
+ var MAX_OUTPUT_BYTES$2, NOTIFICATION_FALLBACK_PREVIEW_BYTES, MAX_FOREGROUND_CAPTURE_BYTES, captureForegroundOutputChunk, createAbortOutcome, SIGTERM_GRACE_MS$1, USER_INTERRUPT_REASON$1, _ALPHABET, BackgroundManager;
29404
29404
  var init_background = __esmMin((() => {
29405
29405
  init_dist$4();
29406
29406
  init_errors$4();
@@ -29415,6 +29415,7 @@ var init_background = __esmMin((() => {
29415
29415
  MAX_OUTPUT_BYTES$2 = 1024 * 1024;
29416
29416
  NOTIFICATION_FALLBACK_PREVIEW_BYTES = 3e3;
29417
29417
  ({ MAX_FOREGROUND_CAPTURE_BYTES, captureForegroundOutputChunk } = createRequire(import.meta.url)("./bin/foreground-output-capture-policy.cjs"));
29418
+ ({ createAbortOutcome } = createRequire(import.meta.url)("./bin/abort-listener-policy.cjs"));
29418
29419
  SIGTERM_GRACE_MS$1 = 5e3;
29419
29420
  USER_INTERRUPT_REASON$1 = "Interrupted by user";
29420
29421
  _ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz";
@@ -29886,6 +29887,7 @@ var init_background = __esmMin((() => {
29886
29887
  });
29887
29888
  const timeout = resettableTimeoutOutcome(entry.options.timeoutMs, { kind: "timeout" });
29888
29889
  entry.timeoutHandle = timeout;
29890
+ const signalOutcome = this.signalOutcome(entry);
29889
29891
  const outcome = await Promise.race([
29890
29892
  worker.then((settlement) => ({
29891
29893
  kind: "worker",
@@ -29896,9 +29898,10 @@ var init_background = __esmMin((() => {
29896
29898
  kind: "stop",
29897
29899
  request
29898
29900
  })),
29899
- this.signalOutcome(entry)
29901
+ signalOutcome.promise
29900
29902
  ]).finally(() => {
29901
29903
  timeout.clear();
29904
+ signalOutcome.cleanup();
29902
29905
  entry.timeoutHandle = void 0;
29903
29906
  });
29904
29907
  const settlement = await this.settlementForOutcome(entry, outcome, worker);
@@ -29906,7 +29909,6 @@ var init_background = __esmMin((() => {
29906
29909
  }
29907
29910
  signalOutcome(entry) {
29908
29911
  const signal = entry.options.signal;
29909
- if (signal === void 0) return new Promise(() => {});
29910
29912
  const outcome = () => ({
29911
29913
  kind: "stop",
29912
29914
  request: {
@@ -29914,12 +29916,7 @@ var init_background = __esmMin((() => {
29914
29916
  abortReason: signal.reason
29915
29917
  }
29916
29918
  });
29917
- if (signal.aborted) return Promise.resolve(outcome());
29918
- return new Promise((resolve) => {
29919
- signal.addEventListener("abort", () => {
29920
- if (!this.isDetached(entry)) resolve(outcome());
29921
- }, { once: true });
29922
- });
29919
+ return createAbortOutcome(signal, outcome, () => !this.isDetached(entry));
29923
29920
  }
29924
29921
  async settlementForOutcome(entry, outcome, worker) {
29925
29922
  if (outcome.kind === "worker") return outcome.settlement;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.293",
3
+ "version": "9.1.295",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {