@event4u/agent-switch 2.3.0 → 2.3.1
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/daemon.js +26 -10
- package/dist/index.js +5 -2
- package/dist/os-notify.js +3 -9
- package/package.json +1 -1
package/dist/daemon.js
CHANGED
|
@@ -46,12 +46,29 @@ import { execFileSync } from "node:child_process";
|
|
|
46
46
|
* daemon-side OS notifications, fire a desktop notification too — marking the
|
|
47
47
|
* record so the GUI does not show it a second time. Deduped events (append
|
|
48
48
|
* returns null) never OS-notify. */
|
|
49
|
-
function notify(event) {
|
|
49
|
+
function notify(event, opts = {}) {
|
|
50
50
|
const created = appendNotification(event);
|
|
51
|
-
if (created && readOsNotifications() && osNotify(event.title, event.message)) {
|
|
51
|
+
if (created && opts.os !== false && readOsNotifications() && osNotify(event.title, event.message)) {
|
|
52
52
|
markOsNotified(created.id);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
/** How long reads must fail before it counts as an outage. A single failed read
|
|
56
|
+
* is routine — a rate-limited endpoint answers again a minute later — and the
|
|
57
|
+
* last known values keep being served meanwhile. */
|
|
58
|
+
export const FETCH_OUTAGE_MS = 15 * 60_000;
|
|
59
|
+
/** The notice for a profile whose reads have failed since `sinceMs`, or null
|
|
60
|
+
* while it is still a blip. Kept off the desktop: a fetch failure is a
|
|
61
|
+
* diagnostic for the app's log, not something to interrupt anyone for. */
|
|
62
|
+
export function fetchFailureNotice(key, sinceMs, now) {
|
|
63
|
+
if (now - sinceMs < FETCH_OUTAGE_MS)
|
|
64
|
+
return null;
|
|
65
|
+
return {
|
|
66
|
+
kind: "warning",
|
|
67
|
+
title: "Usage fetch failed",
|
|
68
|
+
message: `Could not fetch usage limits for ${key} for ${Math.round((now - sinceMs) / 60_000)} min. Keeping the last known values.`,
|
|
69
|
+
os: false,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
55
72
|
// Providers the daemon may suggest a switch for on threshold (they expose a
|
|
56
73
|
// usage readout to base a headroom suggestion on). Antigravity has none.
|
|
57
74
|
const AUTO_PROVIDERS = ["claude", "codex"];
|
|
@@ -319,24 +336,23 @@ async function pollProvider(provider, state, thresholds, notifiedReset, log) {
|
|
|
319
336
|
let failures = 0;
|
|
320
337
|
const polled = [];
|
|
321
338
|
state.notifiedFetchFail = state.notifiedFetchFail ?? {};
|
|
339
|
+
state.fetchFailingSince = state.fetchFailingSince ?? {};
|
|
322
340
|
for (const name of targets) {
|
|
323
341
|
const key = `${provider}/${name}`;
|
|
324
342
|
const snapshot = await snapshotFor(provider, name);
|
|
325
343
|
if (!snapshot) {
|
|
326
344
|
failures++;
|
|
327
345
|
// Fail-safe: the last-known snapshot in state.profiles[key] is left intact
|
|
328
|
-
// (never overwritten with null)
|
|
329
|
-
|
|
330
|
-
|
|
346
|
+
// (never overwritten with null). Only an outage is reported, once.
|
|
347
|
+
const since = state.fetchFailingSince[key] ?? (state.fetchFailingSince[key] = new Date().toISOString());
|
|
348
|
+
const notice = state.notifiedFetchFail[key] ? null : fetchFailureNotice(key, Date.parse(since), Date.now());
|
|
349
|
+
if (notice) {
|
|
331
350
|
state.notifiedFetchFail[key] = true;
|
|
332
|
-
notify({
|
|
333
|
-
kind: "warning",
|
|
334
|
-
title: "Usage fetch failed",
|
|
335
|
-
message: `Could not fetch usage limits for ${provider}/${name}. Keeping the last known values.`,
|
|
336
|
-
});
|
|
351
|
+
notify(notice, { os: notice.os });
|
|
337
352
|
}
|
|
338
353
|
continue;
|
|
339
354
|
}
|
|
355
|
+
delete state.fetchFailingSince[key];
|
|
340
356
|
if (state.notifiedFetchFail[key]) {
|
|
341
357
|
delete state.notifiedFetchFail[key];
|
|
342
358
|
log(`recovered: ${provider}/${name} usage fetch succeeded again`);
|
package/dist/index.js
CHANGED
|
@@ -2058,8 +2058,11 @@ function cmdOsNotify(sub, json = false) {
|
|
|
2058
2058
|
const enabled = readOsNotifications();
|
|
2059
2059
|
if (json)
|
|
2060
2060
|
console.log(JSON.stringify({ enabled }));
|
|
2061
|
-
else
|
|
2061
|
+
else {
|
|
2062
2062
|
console.log(`Daemon OS notifications ${enabled ? "ON" : "OFF"}.`);
|
|
2063
|
+
if (enabled && process.platform === "darwin")
|
|
2064
|
+
console.log("On macOS the daemon shows none itself — the Agent Switch app shows them while it runs.");
|
|
2065
|
+
}
|
|
2063
2066
|
}
|
|
2064
2067
|
/** `agent-switch notifications [clear] [--json]` — list the recent
|
|
2065
2068
|
* notifications (newest first) or clear the log. */
|
|
@@ -2521,7 +2524,7 @@ Run with no command to launch the tray/menubar GUI (single-instance).
|
|
|
2521
2524
|
agent-switch service run|start|stop|status|install|uninstall background usage daemon
|
|
2522
2525
|
agent-switch notifications [clear] [--json] recent notifications (usage-limit suggestions, fetch failures)
|
|
2523
2526
|
agent-switch notify --kind K --title T --message M [--json] record a notification event
|
|
2524
|
-
agent-switch os-notify [on|off|status] [--json] daemon-side OS desktop notifications (default off)
|
|
2527
|
+
agent-switch os-notify [on|off|status] [--json] daemon-side OS desktop notifications, Linux/Windows (default off; macOS: the app shows them)
|
|
2525
2528
|
agent-switch uninstall [--force] remove all agent-switch data + daemon
|
|
2526
2529
|
agent-switch usage history [--provider P] [--profile <name>] [--json] daemon-recorded usage samples per profile
|
|
2527
2530
|
agent-switch tooling [--json] ecosystem tool readout (agent-config, rtk, provider CLIs)
|
package/dist/os-notify.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* Best-effort OS desktop notifications for the background daemon, so an
|
|
3
3
|
* auto-switch is visible even when the GUI is closed. Per-platform native
|
|
4
4
|
* command, no external dependency:
|
|
5
|
-
* - macOS → `osascript
|
|
5
|
+
* - macOS → nothing: an `osascript` notification is attributed to Script
|
|
6
|
+
* Editor. The Agent Switch app shows the notice itself when it
|
|
7
|
+
* runs (records not marked OS-notified), otherwise no one does.
|
|
6
8
|
* - Linux → `notify-send <title> <body>`
|
|
7
9
|
* - Windows → PowerShell balloon tip via System.Windows.Forms.NotifyIcon
|
|
8
10
|
*
|
|
@@ -10,10 +12,6 @@
|
|
|
10
12
|
* testable; `osNotify` wraps it with a fire-and-forget spawn that never throws.
|
|
11
13
|
*/
|
|
12
14
|
import { spawnSync } from "node:child_process";
|
|
13
|
-
/** AppleScript string literal escaping: backslash and double-quote only. */
|
|
14
|
-
function escAppleScript(s) {
|
|
15
|
-
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
16
|
-
}
|
|
17
15
|
/** PowerShell single-quoted string literal escaping: double the single quote. */
|
|
18
16
|
function escPowerShellSingle(s) {
|
|
19
17
|
return s.replace(/'/g, "''");
|
|
@@ -23,10 +21,6 @@ function escPowerShellSingle(s) {
|
|
|
23
21
|
* `platform` matches `process.platform` values (`darwin` / `linux` / `win32`).
|
|
24
22
|
*/
|
|
25
23
|
export function buildOsNotifyCommand(platform, title, body) {
|
|
26
|
-
if (platform === "darwin") {
|
|
27
|
-
const script = `display notification "${escAppleScript(body)}" with title "${escAppleScript(title)}"`;
|
|
28
|
-
return { program: "osascript", args: ["-e", script] };
|
|
29
|
-
}
|
|
30
24
|
if (platform === "linux") {
|
|
31
25
|
// notify-send treats the first arg as summary, the second as body.
|
|
32
26
|
return { program: "notify-send", args: [title, body] };
|
package/package.json
CHANGED