@askalf/dario 4.8.136 → 4.8.137

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/dist/analytics.js CHANGED
@@ -25,6 +25,16 @@ export function billingBucketFromClaim(claim) {
25
25
  switch (claim) {
26
26
  case 'five_hour':
27
27
  case 'seven_day':
28
+ // `*_overage_included` — the plan's INCLUDED overage credit, observed live
29
+ // 2026-07-05 on a Max account at 7d 82% with the `7d_oi` bucket at 99%:
30
+ // fable answered normally (genuine model echo, stop_reason end_turn),
31
+ // status=allowed_warning, overage-utilization 0 — $0 out of pocket, so it
32
+ // is subscription billing, not extra usage. Real paid overage still
33
+ // arrives as `overage` and still halts the guard. Pre-classification the
34
+ // guard's halt-on-unknown design 503'd the proxy on every such claim
35
+ // (30-min cooldown loops) exactly when the weekly window tightens.
36
+ case 'five_hour_overage_included':
37
+ case 'seven_day_overage_included':
28
38
  return 'subscription';
29
39
  case 'five_hour_fallback':
30
40
  case 'seven_day_fallback':
@@ -49,6 +59,8 @@ export const SUBSCRIPTION_CLAIMS = new Set([
49
59
  'seven_day',
50
60
  'five_hour_fallback',
51
61
  'seven_day_fallback',
62
+ 'five_hour_overage_included',
63
+ 'seven_day_overage_included',
52
64
  ]);
53
65
  /**
54
66
  * The sentinel `claim` dario assigns when a response carried no rate-limit
package/dist/notify.d.ts CHANGED
@@ -22,6 +22,22 @@
22
22
  *
23
23
  * See dario#288 — overage-guard.
24
24
  */
25
+ /**
26
+ * Spawn a detached, output-ignored child and swallow EVERY failure mode.
27
+ *
28
+ * The subtle one: a missing binary does NOT throw from `spawn()` — it
29
+ * surfaces as an ASYNC `'error'` event on the ChildProcess, and with no
30
+ * handler attached that's an uncaught exception that kills the whole
31
+ * process. Seen live 2026-07-05 (#672): `spawn notify-send` ENOENT inside
32
+ * the Docker image crashed the proxy on every overage-guard event — the
33
+ * guard fired, notify killed the process, docker restarted it, ~15s of
34
+ * refused connections per event. The existing try/catch only covered the
35
+ * sync-throw path (EMFILE etc.), which is also kept here.
36
+ *
37
+ * Exported for test/notify.mjs, which spawns a guaranteed-nonexistent
38
+ * binary and asserts the process survives the async error event.
39
+ */
40
+ export declare function spawnFireAndForget(cmd: string, args: string[]): void;
25
41
  /**
26
42
  * Fire a native notification. Returns immediately — the underlying spawn
27
43
  * is fire-and-forget. Errors (missing binary, permission denied, no
package/dist/notify.js CHANGED
@@ -24,6 +24,32 @@
24
24
  */
25
25
  import { spawn } from 'node:child_process';
26
26
  import { platform } from 'node:os';
27
+ /**
28
+ * Spawn a detached, output-ignored child and swallow EVERY failure mode.
29
+ *
30
+ * The subtle one: a missing binary does NOT throw from `spawn()` — it
31
+ * surfaces as an ASYNC `'error'` event on the ChildProcess, and with no
32
+ * handler attached that's an uncaught exception that kills the whole
33
+ * process. Seen live 2026-07-05 (#672): `spawn notify-send` ENOENT inside
34
+ * the Docker image crashed the proxy on every overage-guard event — the
35
+ * guard fired, notify killed the process, docker restarted it, ~15s of
36
+ * refused connections per event. The existing try/catch only covered the
37
+ * sync-throw path (EMFILE etc.), which is also kept here.
38
+ *
39
+ * Exported for test/notify.mjs, which spawns a guaranteed-nonexistent
40
+ * binary and asserts the process survives the async error event.
41
+ */
42
+ export function spawnFireAndForget(cmd, args) {
43
+ try {
44
+ const child = spawn(cmd, args, { detached: true, stdio: 'ignore' });
45
+ child.on('error', () => { });
46
+ child.unref();
47
+ }
48
+ catch {
49
+ // Sync throw path (EMFILE and friends); silent — same contract as
50
+ // the notification itself: best-effort, never load-bearing.
51
+ }
52
+ }
27
53
  /**
28
54
  * Fire a native notification. Returns immediately — the underlying spawn
29
55
  * is fire-and-forget. Errors (missing binary, permission denied, no
@@ -47,46 +73,38 @@ export function notify(title, message) {
47
73
  const safeTitle = sanitize(title);
48
74
  const safeMessage = sanitize(message);
49
75
  const plat = platform();
50
- try {
51
- if (plat === 'darwin') {
52
- // AppleScript single-quote inside the script body would need
53
- // escaping; sanitize() strips them so the literal substitution
54
- // below stays safe. Spawned via array argv to avoid a shell
55
- // entirely.
56
- spawn('osascript', [
57
- '-e',
58
- `display notification "${safeMessage}" with title "${safeTitle}"`,
59
- ], { detached: true, stdio: 'ignore' }).unref();
60
- }
61
- else if (plat === 'linux') {
62
- spawn('notify-send', [safeTitle, safeMessage], {
63
- detached: true,
64
- stdio: 'ignore',
65
- }).unref();
66
- }
67
- else if (plat === 'win32') {
68
- // BurntToast is the cleanest path — single-line PowerShell, real
69
- // Windows toast notification. If BurntToast isn't installed the
70
- // command fails silently (stdio: ignore swallows the error
71
- // output), which is the desired behavior.
72
- //
73
- // We don't probe-then-spawn; the cost of one failed BurntToast
74
- // attempt is the same as one probe attempt, and probing makes the
75
- // hot path slower for the success case.
76
- const ps = `try { Import-Module BurntToast -ErrorAction Stop; New-BurntToastNotification -Text '${safeTitle}', '${safeMessage}' } catch { exit 1 }`;
77
- spawn('powershell.exe', [
78
- '-NoProfile',
79
- '-NonInteractive',
80
- '-Command',
81
- ps,
82
- ], { detached: true, stdio: 'ignore' }).unref();
83
- }
84
- // freebsd / openbsd / aix / sunos / android — BEL only. There's no
85
- // single "right" native notification on these platforms.
76
+ if (plat === 'darwin') {
77
+ // AppleScript single-quote inside the script body would need
78
+ // escaping; sanitize() strips them so the literal substitution
79
+ // below stays safe. Spawned via array argv to avoid a shell
80
+ // entirely.
81
+ spawnFireAndForget('osascript', [
82
+ '-e',
83
+ `display notification "${safeMessage}" with title "${safeTitle}"`,
84
+ ]);
86
85
  }
87
- catch {
88
- // spawn() itself can throw on EMFILE or similar; silent.
86
+ else if (plat === 'linux') {
87
+ spawnFireAndForget('notify-send', [safeTitle, safeMessage]);
88
+ }
89
+ else if (plat === 'win32') {
90
+ // BurntToast is the cleanest path — single-line PowerShell, real
91
+ // Windows toast notification. If BurntToast isn't installed the
92
+ // command fails silently (stdio: ignore swallows the error
93
+ // output), which is the desired behavior.
94
+ //
95
+ // We don't probe-then-spawn; the cost of one failed BurntToast
96
+ // attempt is the same as one probe attempt, and probing makes the
97
+ // hot path slower for the success case.
98
+ const ps = `try { Import-Module BurntToast -ErrorAction Stop; New-BurntToastNotification -Text '${safeTitle}', '${safeMessage}' } catch { exit 1 }`;
99
+ spawnFireAndForget('powershell.exe', [
100
+ '-NoProfile',
101
+ '-NonInteractive',
102
+ '-Command',
103
+ ps,
104
+ ]);
89
105
  }
106
+ // freebsd / openbsd / aix / sunos / android — BEL only. There's no
107
+ // single "right" native notification on these platforms.
90
108
  }
91
109
  /**
92
110
  * Strip characters that would break the embedded shell/AppleScript
package/dist/proxy.js CHANGED
@@ -13,7 +13,7 @@ import { buildCCRequest, applyCcPromptCaching, parseEffortSuffix, reverseMapResp
13
13
  import { stampCch, hasCchSeed } from './cch.js';
14
14
  import { describeTemplate, detectDrift, checkCCCompat } from './live-fingerprint.js';
15
15
  import { AccountPool, computeStickyKey, parseRateLimits, modelFamily, isInAuthCooldown, authCooldownMs, reconcilePoolAccounts } from './pool.js';
16
- import { Analytics, billingBucketFromClaim } from './analytics.js';
16
+ import { Analytics, billingBucketFromClaim, SUBSCRIPTION_CLAIMS } from './analytics.js';
17
17
  import { OverageGuard, buildHaltErrorBody } from './overage-guard.js';
18
18
  import { notify as osNotify } from './notify.js';
19
19
  import { loadAllAccounts, loadAccount, refreshAccountToken, resyncLoginFromCredentialsIfStale, ensureLoginCredentialsInPool } from './accounts.js';
@@ -2897,10 +2897,10 @@ export async function startProxy(opts = {}) {
2897
2897
  if (overageUtil !== null) {
2898
2898
  overagePct = `${Math.round(parseFloat(overageUtil) * 100)}%`;
2899
2899
  }
2900
- else if (billingClaim === 'five_hour'
2901
- || billingClaim === 'five_hour_fallback'
2902
- || billingClaim === 'seven_day'
2903
- || billingClaim === 'seven_day_fallback') {
2900
+ else if (billingClaim && SUBSCRIPTION_CLAIMS.has(billingClaim)) {
2901
+ // Any subscription-side claim (incl. *_overage_included) with no
2902
+ // overage-utilization header means $0 extra usage — same sync
2903
+ // source as the guard so this list can't drift again.
2904
2904
  overagePct = '0%';
2905
2905
  }
2906
2906
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "4.8.136",
3
+ "version": "4.8.137",
4
4
  "description": "Use your Claude Pro/Max subscription in any tool — Cursor, Cline, Aider, the Agent SDK, your scripts — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint.",
5
5
  "type": "module",
6
6
  "bin": {