@askalf/dario 4.8.135 → 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
@@ -56,12 +56,33 @@ export declare const CC_TOOL_DEFINITIONS: {
56
56
  description: string;
57
57
  input_schema: Record<string, unknown>;
58
58
  }[];
59
+ /** The UNFILTERED bundled union — every tool the bake knows across platforms
60
+ * (PLATFORM_ONLY_TOOLS keeps the bundle a superset). The identity-mapping,
61
+ * detection, and advertise paths intersect with what the CLIENT declared,
62
+ * and the client's declaration already encodes its platform — a Linux CC
63
+ * never declares PowerShell. Filtering those paths by the PROXY HOST's
64
+ * process.platform (pre-v4.8.136) made a Linux-hosted dario treat a win32
65
+ * client's PowerShell/Glob/Grep as non-native: PowerShell fell to the
66
+ * unmapped round-robin, and all three were dropped from the advertised
67
+ * array (Glob/Grep translated via lowercase aliases but were never sent
68
+ * upstream). Host-filtered CC_TOOL_DEFINITIONS stays correct for the paths
69
+ * with no client declaration to mirror: the full-template fallback, the
70
+ * merge-mode base array, and Fable's no-tools shape. */
71
+ export declare const CC_TOOL_DEFINITIONS_UNION: {
72
+ name: string;
73
+ description: string;
74
+ input_schema: Record<string, unknown>;
75
+ }[];
76
+ export declare const CC_NATIVE_NAMES_UNION: Set<string>;
59
77
  /** CC's own tool names, EXACT case ("Read", "Bash", "Agent", …). A CC client's
60
78
  * tools identity-map to themselves and OVERRIDE TOOL_MAP — whose lowercase
61
79
  * cross-client aliases ('read' → {path}/{filePath}) would otherwise mistranslate
62
80
  * a CC tool (Read's file_path → path). Exact case is the discriminator: CC sends
63
81
  * PascalCase, the {path}-style clients send lowercase/snake, so a non-CC `read`
64
- * still routes through TOOL_MAP. Tracks the live bundle (refreshed each bake). */
82
+ * still routes through TOOL_MAP. Tracks the live bundle (refreshed each bake).
83
+ * HOST-filtered — the routing, detection, and advertise paths use the
84
+ * _UNION variants below so a client on a different platform than the proxy
85
+ * host still identity-maps (v4.8.136). */
65
86
  export declare const CC_NATIVE_NAMES: Set<string>;
66
87
  /** MCP tools attached to a CC session are namespaced `mcp__<server>__<tool>`.
67
88
  * Real CC advertises them VERBATIM after its built-ins — the schemas are
@@ -66,12 +66,29 @@ export const INTERACTIVE_ONLY_TOOLS = new Set([
66
66
  ]);
67
67
  /** CC's exact tool definitions for the current platform — filtered from the bundled union. */
68
68
  export const CC_TOOL_DEFINITIONS = filterToolsForPlatform(TEMPLATE.tools, process.platform);
69
+ /** The UNFILTERED bundled union — every tool the bake knows across platforms
70
+ * (PLATFORM_ONLY_TOOLS keeps the bundle a superset). The identity-mapping,
71
+ * detection, and advertise paths intersect with what the CLIENT declared,
72
+ * and the client's declaration already encodes its platform — a Linux CC
73
+ * never declares PowerShell. Filtering those paths by the PROXY HOST's
74
+ * process.platform (pre-v4.8.136) made a Linux-hosted dario treat a win32
75
+ * client's PowerShell/Glob/Grep as non-native: PowerShell fell to the
76
+ * unmapped round-robin, and all three were dropped from the advertised
77
+ * array (Glob/Grep translated via lowercase aliases but were never sent
78
+ * upstream). Host-filtered CC_TOOL_DEFINITIONS stays correct for the paths
79
+ * with no client declaration to mirror: the full-template fallback, the
80
+ * merge-mode base array, and Fable's no-tools shape. */
81
+ export const CC_TOOL_DEFINITIONS_UNION = TEMPLATE.tools;
82
+ export const CC_NATIVE_NAMES_UNION = new Set(TEMPLATE.tools.map((t) => String(t.name)));
69
83
  /** CC's own tool names, EXACT case ("Read", "Bash", "Agent", …). A CC client's
70
84
  * tools identity-map to themselves and OVERRIDE TOOL_MAP — whose lowercase
71
85
  * cross-client aliases ('read' → {path}/{filePath}) would otherwise mistranslate
72
86
  * a CC tool (Read's file_path → path). Exact case is the discriminator: CC sends
73
87
  * PascalCase, the {path}-style clients send lowercase/snake, so a non-CC `read`
74
- * still routes through TOOL_MAP. Tracks the live bundle (refreshed each bake). */
88
+ * still routes through TOOL_MAP. Tracks the live bundle (refreshed each bake).
89
+ * HOST-filtered — the routing, detection, and advertise paths use the
90
+ * _UNION variants below so a client on a different platform than the proxy
91
+ * host still identity-maps (v4.8.136). */
75
92
  export const CC_NATIVE_NAMES = new Set(CC_TOOL_DEFINITIONS.map((t) => String(t.name)));
76
93
  /** MCP tools attached to a CC session are namespaced `mcp__<server>__<tool>`.
77
94
  * Real CC advertises them VERBATIM after its built-ins — the schemas are
@@ -551,7 +568,9 @@ export function detectNonCCByTools(clientTools) {
551
568
  // routing agree. An ALL-mcp surface now scores ratio 0 and stays in default
552
569
  // mode — safe, because the advertise path sends an mcp-only declaration
553
570
  // verbatim rather than falling back to the full CC template.
554
- if (!TOOL_MAP[rawName.toLowerCase()] && !CC_NATIVE_NAMES.has(rawName) && !isMcpToolName(rawName))
571
+ // Union set, not the host-filtered one: a win32 client's PowerShell is
572
+ // native regardless of the platform dario itself runs on (v4.8.136).
573
+ if (!TOOL_MAP[rawName.toLowerCase()] && !CC_NATIVE_NAMES_UNION.has(rawName) && !isMcpToolName(rawName))
555
574
  unmapped++;
556
575
  }
557
576
  const ratio = unmapped / clientTools.length;
@@ -1360,7 +1379,10 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1360
1379
  // Exact case is the discriminator (CC sends PascalCase; {path}-style clients
1361
1380
  // send lowercase/snake) so a genuine non-CC `read` still routes via TOOL_MAP.
1362
1381
  // Tracks the live bundle, so future CC tools are covered after the next bake.
1363
- const mapping = CC_NATIVE_NAMES.has(tool.name) || isMcpToolName(tool.name)
1382
+ // UNION set: identity must hold for a win32 client's PowerShell/Glob/Grep
1383
+ // even when dario itself runs on Linux (v4.8.136) — the exact-case check
1384
+ // still keeps a non-CC lowercase `glob`/`grep` on its TOOL_MAP alias.
1385
+ const mapping = CC_NATIVE_NAMES_UNION.has(tool.name) || isMcpToolName(tool.name)
1364
1386
  ? { ccTool: tool.name, translateArgs: (a) => a, translateBack: (a) => a }
1365
1387
  : TOOL_MAP[name];
1366
1388
  if (mapping) {
@@ -1398,8 +1420,8 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1398
1420
  const CC_FALLBACK_TOOLS = ['Bash', 'Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch'];
1399
1421
  for (const tool of clientTools) {
1400
1422
  const name = (tool.name || '').toLowerCase();
1401
- if (CC_NATIVE_NAMES.has(tool.name) || isMcpToolName(tool.name) || TOOL_MAP[name])
1402
- continue; // CC-native / MCP (identity in pass 1) or mapped
1423
+ if (CC_NATIVE_NAMES_UNION.has(tool.name) || isMcpToolName(tool.name) || TOOL_MAP[name])
1424
+ continue; // CC-native (union) / MCP (identity in pass 1) or mapped
1403
1425
  unmappedTools.push(tool.name);
1404
1426
  if (opts.hybridTools)
1405
1427
  continue; // dropped — see comment above
@@ -1592,7 +1614,11 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1592
1614
  const clientToolNames = new Set(clientTools
1593
1615
  .map((t) => t.name?.toLowerCase())
1594
1616
  .filter((n) => Boolean(n)));
1595
- const availableCC = CC_TOOL_DEFINITIONS.filter((t) => clientToolNames.has(t.name.toLowerCase()));
1617
+ // Intersect against the UNION, not the host-filtered set: the client's
1618
+ // declaration encodes the client's platform, so a win32 CC declaring
1619
+ // PowerShell/Glob/Grep gets their canonical defs even from a Linux-hosted
1620
+ // dario. The host filter only governs the no-declaration fallbacks.
1621
+ const availableCC = CC_TOOL_DEFINITIONS_UNION.filter((t) => clientToolNames.has(t.name.toLowerCase()));
1596
1622
  const mcpTools = clientTools.filter((t) => isMcpToolName(t.name));
1597
1623
  ccRequest.tools = availableCC.length > 0 || mcpTools.length > 0
1598
1624
  ? [...availableCC, ...mcpTools]
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.135",
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": {