dsh-agy-link 0.4.53 → 0.4.57

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/index.js CHANGED
@@ -129,6 +129,35 @@ function looksLikeNetworkError(message) {
129
129
  return /dial tcp|fetch failed|econn(?:refused|reset|aborted)|enotfound|ehostunreach|enetunreach|socket hang up|getaddrinfo|bad gateway|service unavailable|unexpected eof|\beof\b|tls handshake timeout|i\/o timeout|network (?:error|is unreachable)|no route to host|connection reset|context deadline exceeded|temporary failure in name resolution|proxyconnect|token validation failed|validate (?:the )?token|silent auth failed/i.test(message);
130
130
  }
131
131
  /**
132
+ * Evidence from agy's OWN per-run log that a stored credential was present but
133
+ * could not be VALIDATED over the network — i.e. jitter wearing an auth costume.
134
+ *
135
+ * The stderr banner agy prints when silent auth fails is byte-identical for a
136
+ * genuine sign-out and for a keyring validation that missed its hard 10s
137
+ * window, so the banner cannot decide AUTH vs NETWORK. The log can:
138
+ *
139
+ * real sign-out printmode.go:403 not authenticated, trying silent auth
140
+ * printmode.go:409 silent auth failed ← 29 ms later,
141
+ * (NO keyringAuth: line — nothing to validate) no keyring line
142
+ * network blip printmode.go:403 not authenticated, trying silent auth
143
+ * keyring.go:64 keyringAuth: loaded token … expired=false
144
+ * keyring.go:111 keyringAuth: timed out after 10s
145
+ * printmode.go:409 silent auth failed
146
+ * keyring.go:93 keyringAuth: failed to fetch user info:
147
+ * userinfo request failed: … i/o timeout
148
+ *
149
+ * `keyringAuth:` lines only exist when agy actually HAD a credential to
150
+ * validate, and the timeout / userinfo markers are transport failures inside
151
+ * that validation. Verified against real logs (2026-10-16): the sign-out log
152
+ * contains 0 occurrences of every marker below, the blip log contains all of
153
+ * them.
154
+ */
155
+ function looksLikeAuthVerificationJitter(agyLogText) {
156
+ if (agyLogText === "") return false;
157
+ if (isAuthoritativeAuthError(agyLogText)) return false;
158
+ return /keyringAuth: timed out after|failed to fetch user info|userinfo request failed/i.test(agyLogText);
159
+ }
160
+ /**
132
161
  * Decide whether a failed call may flag an account as "needs re-login".
133
162
  * Genuine AUTH classification wins (unless the failure was transport-shaped),
134
163
  * and the message must name an authoritative token state otherwise.
@@ -421,10 +450,18 @@ function resolveConfig(entry, env = process.env, overrides = readOverrides()) {
421
450
  }
422
451
  //#endregion
423
452
  //#region src/common/pool-types.ts
424
- /** Map a model slug to its backend quota counter family. */
453
+ /**
454
+ * Map a model slug to its backend quota counter family.
455
+ *
456
+ * Gemini 4 Argon (Pro/Ultra-only) bills against its OWN weekly quota pool —
457
+ * never the plain Gemini one. Its ids look like `gemini-4-argon-low/-medium`
458
+ * and MUST be detected before the generic `gemini-` prefix below, otherwise
459
+ * argon usage would silently drain/report the Gemini counter.
460
+ */
425
461
  function modelFamilyOf(modelId) {
426
462
  if (!modelId) return "unknown";
427
463
  const id = modelId.toLowerCase();
464
+ if (id.includes("argon")) return "argon";
428
465
  if (id.startsWith("claude-") || id.includes("claude")) return "anthropic";
429
466
  if (id.startsWith("gemini-") || id.startsWith("gemma-") || id.includes("gemini")) return "google";
430
467
  if (id.startsWith("gpt-") || id.startsWith("openai/") || id.includes("gpt-oss")) return "openai";
@@ -2824,6 +2861,209 @@ function ensureIsolatedKeychain(dir, platform = process.platform, run = (file, a
2824
2861
  return false;
2825
2862
  }
2826
2863
  }
2864
+ /**
2865
+ * Purge any stale locked keychain from an isolated account HOME directory.
2866
+ * Keychains from older versions (v0.4.41-v0.4.44) were provisioned with
2867
+ * ephemeral random passwords that cannot be unlocked with an empty password,
2868
+ * causing repeated modal password dialogs from macOS SecurityAgent.
2869
+ * If unlocking with `-p ""` fails, the file and its registration in securityd
2870
+ * are purged so that ensureIsolatedKeychain can cleanly re-provision it.
2871
+ */
2872
+ function purgeStaleIsolatedKeychain(dir, platform = process.platform, run = (file, args, env) => execFileSync(file, args, {
2873
+ stdio: "ignore",
2874
+ env,
2875
+ timeout: 5e3
2876
+ })) {
2877
+ if (platform !== "darwin") return false;
2878
+ if (dir === "") return false;
2879
+ const kc = join(dir, "Library", "Keychains", "login.keychain-db");
2880
+ if (!existsSync(kc)) return false;
2881
+ const env = {
2882
+ ...process.env,
2883
+ HOME: dir
2884
+ };
2885
+ try {
2886
+ run("security", [
2887
+ "unlock-keychain",
2888
+ "-p",
2889
+ "",
2890
+ kc
2891
+ ], env);
2892
+ return false;
2893
+ } catch {
2894
+ try {
2895
+ run("security", ["delete-keychain", kc], env);
2896
+ } catch {}
2897
+ try {
2898
+ rmSync(kc, { force: true });
2899
+ } catch {}
2900
+ return true;
2901
+ }
2902
+ }
2903
+ /**
2904
+ * macOS: Repair hijacked user-level keychain configuration.
2905
+ *
2906
+ * Versions prior to v0.4.49 set `security default-keychain -s <isolated-kc>`,
2907
+ * redirecting the user's desktop session default and login keychain into an
2908
+ * account-specific folder under ~/.dsh/agy-accounts/.
2909
+ *
2910
+ * This function detects if default-keychain, login-keychain, or the search list
2911
+ * (`list-keychains -d user`) point to any path containing `agy-accounts` or `.dsh`,
2912
+ * and restores them to the user's real login keychain (`~/Library/Keychains/login.keychain-db`).
2913
+ */
2914
+ function repairMacSystemKeychain(userHome = homedir(), platform = process.platform, run = (file, args, env) => execFileSync(file, args, {
2915
+ encoding: "utf8",
2916
+ env: env ?? process.env,
2917
+ timeout: 5e3
2918
+ })) {
2919
+ if (platform !== "darwin") return false;
2920
+ if (!userHome) return false;
2921
+ let repaired = false;
2922
+ const realKc = [join(userHome, "Library", "Keychains", "login.keychain-db"), join(userHome, "Library", "Keychains", "login.keychain")].find((p) => existsSync(p));
2923
+ if (!realKc) return false;
2924
+ const isIsolatedPath = (p) => /[\\/]\.dsh[\\/]agy-accounts[\\/]/i.test(p);
2925
+ const env = {
2926
+ ...process.env,
2927
+ HOME: userHome
2928
+ };
2929
+ try {
2930
+ if (isIsolatedPath(run("security", ["default-keychain"], env).trim().replace(/^"|"$/g, ""))) {
2931
+ run("security", [
2932
+ "default-keychain",
2933
+ "-s",
2934
+ realKc
2935
+ ], env);
2936
+ repaired = true;
2937
+ }
2938
+ } catch {}
2939
+ try {
2940
+ if (isIsolatedPath(run("security", ["login-keychain"], env).trim().replace(/^"|"$/g, ""))) {
2941
+ run("security", [
2942
+ "login-keychain",
2943
+ "-s",
2944
+ realKc
2945
+ ], env);
2946
+ repaired = true;
2947
+ }
2948
+ } catch {}
2949
+ try {
2950
+ const entries = run("security", [
2951
+ "list-keychains",
2952
+ "-d",
2953
+ "user"
2954
+ ], env).split("\n").map((l) => l.trim().replace(/^"|"$/g, "")).filter(Boolean);
2955
+ const hasTainted = entries.some(isIsolatedPath);
2956
+ const hasReal = entries.includes(realKc);
2957
+ if (hasTainted || !hasReal) {
2958
+ const cleaned = entries.filter((p) => !isIsolatedPath(p));
2959
+ if (!cleaned.includes(realKc)) cleaned.unshift(realKc);
2960
+ const sysKc = "/Library/Keychains/System.keychain";
2961
+ if (!cleaned.includes(sysKc) && existsSync(sysKc)) cleaned.push(sysKc);
2962
+ run("security", [
2963
+ "list-keychains",
2964
+ "-d",
2965
+ "user",
2966
+ "-s",
2967
+ ...cleaned
2968
+ ], env);
2969
+ repaired = true;
2970
+ }
2971
+ } catch {}
2972
+ return repaired;
2973
+ }
2974
+ /**
2975
+ * Open an interactive terminal session inside the given account directory.
2976
+ * Supports macOS (Terminal.app via AppleScript), Windows (cmd.exe start),
2977
+ * and Linux (xdg-terminal-exec, x-terminal-emulator, gnome-terminal, konsole,
2978
+ * xfce4-terminal, alacritty, xterm across distributions).
2979
+ * Safe, non-blocking and non-throwing.
2980
+ */
2981
+ function openAccountTerminal(accDir, platform = process.platform, run = (file, args, opts) => execFile(file, [...args], { windowsHide: opts?.windowsHide }, () => {})) {
2982
+ if (!accDir) return false;
2983
+ if (platform === "darwin") {
2984
+ const script = `tell application "Terminal" to activate\ntell application "Terminal" to do script "export HOME='${accDir}'; agy"`;
2985
+ try {
2986
+ run("osascript", ["-e", script]);
2987
+ return true;
2988
+ } catch {
2989
+ return false;
2990
+ }
2991
+ }
2992
+ if (platform === "win32") try {
2993
+ run("cmd.exe", [
2994
+ "/c",
2995
+ "start",
2996
+ "cmd.exe",
2997
+ "/k",
2998
+ `set "HOME=${accDir}" && set "USERPROFILE=${accDir}" && agy`
2999
+ ], { windowsHide: true });
3000
+ return true;
3001
+ } catch {
3002
+ return false;
3003
+ }
3004
+ const cmd = `export HOME='${accDir}'; agy; exec bash`;
3005
+ const candidates = [
3006
+ {
3007
+ bin: "xdg-terminal-exec",
3008
+ args: [
3009
+ "bash",
3010
+ "-c",
3011
+ cmd
3012
+ ]
3013
+ },
3014
+ {
3015
+ bin: "x-terminal-emulator",
3016
+ args: ["-e", `sh -c "export HOME='${accDir}'; agy; exec sh"`]
3017
+ },
3018
+ {
3019
+ bin: "gnome-terminal",
3020
+ args: [
3021
+ "--",
3022
+ "bash",
3023
+ "-c",
3024
+ cmd
3025
+ ]
3026
+ },
3027
+ {
3028
+ bin: "konsole",
3029
+ args: [
3030
+ "-e",
3031
+ "bash",
3032
+ "-c",
3033
+ cmd
3034
+ ]
3035
+ },
3036
+ {
3037
+ bin: "xfce4-terminal",
3038
+ args: ["-e", `bash -c "${cmd}"`]
3039
+ },
3040
+ {
3041
+ bin: "alacritty",
3042
+ args: [
3043
+ "-e",
3044
+ "bash",
3045
+ "-c",
3046
+ cmd
3047
+ ]
3048
+ },
3049
+ {
3050
+ bin: "xterm",
3051
+ args: [
3052
+ "-e",
3053
+ "bash",
3054
+ "-c",
3055
+ cmd
3056
+ ]
3057
+ }
3058
+ ];
3059
+ for (const c of candidates) try {
3060
+ run(c.bin, c.args);
3061
+ return true;
3062
+ } catch {
3063
+ continue;
3064
+ }
3065
+ return false;
3066
+ }
2827
3067
  function resolveAgyBin(cfg) {
2828
3068
  const candidates = [];
2829
3069
  if (cfg.agyBin !== "") candidates.push(cfg.agyBin);
@@ -3307,15 +3547,42 @@ function parseWindowsProxyServer(raw) {
3307
3547
  }
3308
3548
  return /^https?:\/\//i.test(s) ? s : `http://${s}`;
3309
3549
  }
3550
+ /**
3551
+ * Parse macOS `scutil --proxy` dictionary output.
3552
+ * Keys of interest:
3553
+ * HTTPEnable : 1
3554
+ * HTTPPort : 7890
3555
+ * HTTPProxy : 127.0.0.1
3556
+ * HTTPSEnable : 1
3557
+ * HTTPSPort : 7890
3558
+ * HTTPSProxy : 127.0.0.1
3559
+ * SOCKSEnable : 1
3560
+ * SOCKSPort : 7890
3561
+ * SOCKSProxy : 127.0.0.1
3562
+ */
3563
+ function parseMacScutilProxy(raw) {
3564
+ if (!raw.trim()) return void 0;
3565
+ const lines = raw.split("\n");
3566
+ const dict = {};
3567
+ for (const line of lines) {
3568
+ const m = /^\s*([A-Za-z0-9_]+)\s*:\s*(.+)$/.exec(line);
3569
+ if (m && m[1] && m[2]) dict[m[1]] = m[2].trim();
3570
+ }
3571
+ if (dict.HTTPSEnable === "1" && dict.HTTPSProxy && dict.HTTPSPort) return `http://${dict.HTTPSProxy}:${dict.HTTPSPort}`;
3572
+ if (dict.HTTPEnable === "1" && dict.HTTPProxy && dict.HTTPPort) return `http://${dict.HTTPProxy}:${dict.HTTPPort}`;
3573
+ if (dict.SOCKSEnable === "1" && dict.SOCKSProxy && dict.SOCKSPort) return `socks5://${dict.SOCKSProxy}:${dict.SOCKSPort}`;
3574
+ }
3310
3575
  let cachedProxy = null;
3311
3576
  /**
3312
- * Detect system proxy configuration.
3577
+ * Detect system proxy configuration across Windows, macOS, and Linux.
3313
3578
  * Priority:
3314
- * 1. Standard environment variables (HTTPS_PROXY, HTTP_PROXY, ALL_PROXY)
3579
+ * 1. Standard environment variables (HTTPS_PROXY, HTTP_PROXY, ALL_PROXY, and lowercase)
3315
3580
  * 2. Windows Registry (HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings)
3316
- * Results are cached for 5 seconds to minimize registry queries.
3581
+ * 3. macOS scutil (--proxy)
3582
+ * 4. Linux desktop system proxy (GNOME gsettings)
3583
+ * Results are cached for 5 minutes (300s) to minimize subprocess and registry queries.
3317
3584
  */
3318
- function detectSystemProxy() {
3585
+ function detectSystemProxy(platform = process.platform) {
3319
3586
  const now = Date.now();
3320
3587
  if (cachedProxy && cachedProxy.expiresAt > now) return cachedProxy.value;
3321
3588
  const envProxy = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || process.env.ALL_PROXY || process.env.all_proxy;
@@ -3327,7 +3594,7 @@ function detectSystemProxy() {
3327
3594
  };
3328
3595
  return val;
3329
3596
  }
3330
- if (IS_WIN$1) try {
3597
+ if (platform === "win32") try {
3331
3598
  const out = execFileSync("reg", ["query", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"], {
3332
3599
  encoding: "utf8",
3333
3600
  timeout: 2e3,
@@ -3353,6 +3620,74 @@ function detectSystemProxy() {
3353
3620
  }
3354
3621
  }
3355
3622
  } catch {}
3623
+ if (platform === "darwin") try {
3624
+ const val = parseMacScutilProxy(execFileSync("scutil", ["--proxy"], {
3625
+ encoding: "utf8",
3626
+ timeout: 2e3,
3627
+ stdio: [
3628
+ "ignore",
3629
+ "pipe",
3630
+ "ignore"
3631
+ ]
3632
+ }));
3633
+ if (val) {
3634
+ cachedProxy = {
3635
+ value: val,
3636
+ expiresAt: now + 3e5
3637
+ };
3638
+ return val;
3639
+ }
3640
+ } catch {}
3641
+ if (platform === "linux") try {
3642
+ if (execFileSync("gsettings", [
3643
+ "get",
3644
+ "org.gnome.system.proxy",
3645
+ "mode"
3646
+ ], {
3647
+ encoding: "utf8",
3648
+ timeout: 1e3,
3649
+ stdio: [
3650
+ "ignore",
3651
+ "pipe",
3652
+ "ignore"
3653
+ ]
3654
+ }).trim().replace(/^'|'$/g, "") === "manual") {
3655
+ const host = execFileSync("gsettings", [
3656
+ "get",
3657
+ "org.gnome.system.proxy.https",
3658
+ "host"
3659
+ ], {
3660
+ encoding: "utf8",
3661
+ timeout: 1e3,
3662
+ stdio: [
3663
+ "ignore",
3664
+ "pipe",
3665
+ "ignore"
3666
+ ]
3667
+ }).trim().replace(/^'|'$/g, "");
3668
+ const port = execFileSync("gsettings", [
3669
+ "get",
3670
+ "org.gnome.system.proxy.https",
3671
+ "port"
3672
+ ], {
3673
+ encoding: "utf8",
3674
+ timeout: 1e3,
3675
+ stdio: [
3676
+ "ignore",
3677
+ "pipe",
3678
+ "ignore"
3679
+ ]
3680
+ }).trim();
3681
+ if (host && port && port !== "0") {
3682
+ const val = `http://${host}:${port}`;
3683
+ cachedProxy = {
3684
+ value: val,
3685
+ expiresAt: now + 3e5
3686
+ };
3687
+ return val;
3688
+ }
3689
+ }
3690
+ } catch {}
3356
3691
  cachedProxy = {
3357
3692
  value: void 0,
3358
3693
  expiresAt: now + 3e5
@@ -3375,6 +3710,13 @@ var AccountPoolManager = class {
3375
3710
  * sessions.json: web + headless share this file).
3376
3711
  */
3377
3712
  knownIds = /* @__PURE__ */ new Set();
3713
+ /**
3714
+ * Consecutive non-authoritative auth failures per account id (noteAuthFailure).
3715
+ * Deliberately in-memory: losing the streak on restart only costs one extra
3716
+ * confirmation round, while persisting it could quarantine an account for a
3717
+ * blip that healed while the process was down.
3718
+ */
3719
+ authStrikes = /* @__PURE__ */ new Map();
3378
3720
  /** Serialized snapshot of the last successful persist/load; skips no-op writes. */
3379
3721
  persistedJson = "";
3380
3722
  constructor(baseDir = defaultPoolDir()) {
@@ -3717,9 +4059,34 @@ var AccountPoolManager = class {
3717
4059
  if (this.data.activeAccountIds) {
3718
4060
  for (const [fam, accId] of Object.entries(this.data.activeAccountIds)) if (accId === id) delete this.data.activeAccountIds[fam];
3719
4061
  }
4062
+ this.authStrikes.delete(id);
3720
4063
  this.persist();
3721
4064
  }
3722
4065
  /**
4066
+ * Confirmation-gated quarantine for auth-classified failures that carry NO
4067
+ * authoritative evidence (no invalid_grant / revoked / unauthorized_client).
4068
+ *
4069
+ * agy prints the same "not signed in" banner whether the login is really
4070
+ * dead or its keyring validation merely missed a 10s window on a flaky
4071
+ * network, so one banner used to sticky-quarantine healthy accounts — the
4072
+ * user then saw a red 需重新登录 and could not even 设为当前使用. A single
4073
+ * strike now only rotates the account away for the current retry; the flag
4074
+ * hardens into a real quarantine only when the failure repeats with no
4075
+ * success in between. Authoritative OAuth paths keep calling
4076
+ * markAuthRequired directly and quarantine on first sight.
4077
+ *
4078
+ * Returns true when the account is now hard-quarantined.
4079
+ */
4080
+ noteAuthFailure(id, reason) {
4081
+ const strikes = (this.authStrikes.get(id) ?? 0) + 1;
4082
+ if (strikes < 2) {
4083
+ this.authStrikes.set(id, strikes);
4084
+ return false;
4085
+ }
4086
+ this.markAuthRequired(id, reason);
4087
+ return true;
4088
+ }
4089
+ /**
3723
4090
  * External re-login detected (agy logout + new login): identity-bound
3724
4091
  * state from the PREVIOUS account (cooldowns, quotas, auth quarantine)
3725
4092
  * must not leak onto the new one. Resets everything email-bound while
@@ -3742,6 +4109,7 @@ var AccountPoolManager = class {
3742
4109
  delete acc.authRequired;
3743
4110
  delete acc.authError;
3744
4111
  delete acc.authMarkedAt;
4112
+ this.authStrikes.delete(id);
3745
4113
  this.persist();
3746
4114
  }
3747
4115
  /**
@@ -3958,6 +4326,7 @@ var AccountPoolManager = class {
3958
4326
  delete acc.authMarkedAt;
3959
4327
  }
3960
4328
  if (acc.cooldowns?.[family]) delete acc.cooldowns[family];
4329
+ this.authStrikes.delete(id);
3961
4330
  this.persist();
3962
4331
  }
3963
4332
  clearCooldown(id, family) {
@@ -27547,10 +27916,10 @@ const OAUTH_USERINFO_URL$1 = "https://www.googleapis.com/oauth2/v1/userinfo";
27547
27916
  /** Fixed loopback callback registered for the Antigravity client (like opencode). */
27548
27917
  const OAUTH_CALLBACK_PORT = 51121;
27549
27918
  const OAUTH_REDIRECT_URI = `http://localhost:${OAUTH_CALLBACK_PORT}/oauth-callback`;
27550
- /** Runtime endpoint fallback order (daily first, mirroring OmniRoute). */
27919
+ /** Runtime endpoint fallback order (official production first, then staging/daily fallbacks). */
27551
27920
  const AGY_ENDPOINTS = [
27552
- "https://daily-cloudcode-pa.googleapis.com",
27553
27921
  "https://cloudcode-pa.googleapis.com",
27922
+ "https://daily-cloudcode-pa.googleapis.com",
27554
27923
  "https://daily-cloudcode-pa.sandbox.googleapis.com",
27555
27924
  "https://autopush-cloudcode-pa.sandbox.googleapis.com"
27556
27925
  ];
@@ -28166,6 +28535,58 @@ function findDuplicateCredentialIdentities(accounts, homeDir) {
28166
28535
  return reports;
28167
28536
  }
28168
28537
  //#endregion
28538
+ //#region src/host/agy-log.ts
28539
+ /** Same home resolution quota.ts uses for per-account credential access. */
28540
+ function agyHomeDirFor(account) {
28541
+ return account?.systemHome || !account?.dir ? homedir() : account.dir;
28542
+ }
28543
+ function agyLogDir(homeDir) {
28544
+ return join(homeDir, ".gemini", "antigravity-cli", "log");
28545
+ }
28546
+ /** Names of the cli-*.log files present right now (cheap readdir). */
28547
+ function snapshotAgyLogs(homeDir) {
28548
+ const dir = agyLogDir(homeDir);
28549
+ try {
28550
+ return new Set(readdirSync(dir).filter((f) => f.startsWith("cli-") && f.endsWith(".log")));
28551
+ } catch {
28552
+ return /* @__PURE__ */ new Set();
28553
+ }
28554
+ }
28555
+ /** Upper bound so one pathological log cannot balloon the failure path. */
28556
+ const MAX_LOG_BYTES = 262144;
28557
+ const MAX_LOG_FILES = 3;
28558
+ /**
28559
+ * Concatenated text of the log files THIS run created (present now, absent
28560
+ * from the pre-spawn snapshot), newest first. Returns '' when the run wrote no
28561
+ * readable log — callers must treat that as "no evidence", never as proof
28562
+ * either way.
28563
+ */
28564
+ function readAgyRunLogText(homeDir, before) {
28565
+ const dir = agyLogDir(homeDir);
28566
+ let names;
28567
+ try {
28568
+ if (!existsSync(dir)) return "";
28569
+ names = readdirSync(dir).filter((f) => f.startsWith("cli-") && f.endsWith(".log") && !before.has(f)).map((f) => ({
28570
+ f,
28571
+ m: statSync(join(dir, f)).mtimeMs
28572
+ })).sort((a, b) => b.m - a.m).slice(0, MAX_LOG_FILES).map((e) => e.f);
28573
+ } catch {
28574
+ return "";
28575
+ }
28576
+ const parts = [];
28577
+ let budget = MAX_LOG_BYTES;
28578
+ for (const name of names) {
28579
+ if (budget <= 0) break;
28580
+ try {
28581
+ const text = readFileSync(join(dir, name), "utf8");
28582
+ const slice = text.length > budget ? text.slice(-budget) : text;
28583
+ parts.push(slice);
28584
+ budget -= slice.length;
28585
+ } catch {}
28586
+ }
28587
+ return parts.join("\n");
28588
+ }
28589
+ //#endregion
28169
28590
  //#region src/host/sessions.ts
28170
28591
  var SessionStore = class {
28171
28592
  file;
@@ -29248,9 +29669,13 @@ var AgyAdapter = class AgyAdapter extends LlmAdapter {
29248
29669
  * { model: LlmResolvedModelInfo; stream(options): AsyncIterable<StreamChunk> }.
29249
29670
  */
29250
29671
  async prepareCall(provider, model, signal) {
29672
+ if (signal?.aborted) throw signal.reason ?? new LlmError("aborted", "ABORTED");
29251
29673
  return {
29252
29674
  model: await this.resolveModel(provider, model, signal),
29253
- stream: (options) => this.stream(options)
29675
+ stream: (options) => this.stream({
29676
+ ...options,
29677
+ signal: options.signal ?? signal
29678
+ })
29254
29679
  };
29255
29680
  }
29256
29681
  /** Build the agy argv for one call. Exported for tests. */
@@ -29315,7 +29740,7 @@ var AgyAdapter = class AgyAdapter extends LlmAdapter {
29315
29740
  const rec = this.deps.runs.get(continuation.runId);
29316
29741
  const deadFailure = rec !== void 0 && rec.isSettled ? rec.failureInfo : null;
29317
29742
  if (rec !== void 0 && !(deadFailure !== null && this.claimFailover(sessionKey, deadFailure.code))) {
29318
- yield* this.driveSpan(rec, continuation.eventIndex + 1, hasToolSupport, isCodeMode, goalContext, supportsGoalUpdate, supportsPlanExit);
29743
+ yield* this.driveSpan(rec, continuation.eventIndex + 1, hasToolSupport, isCodeMode, goalContext, supportsGoalUpdate, supportsPlanExit, options.signal);
29319
29744
  return;
29320
29745
  }
29321
29746
  if (rec !== void 0) {
@@ -29548,7 +29973,7 @@ var AgyAdapter = class AgyAdapter extends LlmAdapter {
29548
29973
  startedAt: now,
29549
29974
  inFlight: true
29550
29975
  });
29551
- yield* this.driveSpan(liveRun, rescueFrom, hasToolSupport, isCodeMode, goalContext, supportsGoalUpdate, supportsPlanExit);
29976
+ yield* this.driveSpan(liveRun, rescueFrom, hasToolSupport, isCodeMode, goalContext, supportsGoalUpdate, supportsPlanExit, options.signal);
29552
29977
  return;
29553
29978
  }
29554
29979
  throw new LlmError("Duplicate request ignored: an identical request is already running for this session.", Err.BUSY);
@@ -29659,7 +30084,7 @@ var AgyAdapter = class AgyAdapter extends LlmAdapter {
29659
30084
  promptViaStdin: true
29660
30085
  }) : argsBase;
29661
30086
  const stdinPayload = promptViaStdin ? buildStreamInputLine(prompt) : void 0;
29662
- const release = await this.deps.acquire();
30087
+ const release = await this.deps.acquire(options.signal);
29663
30088
  let released = false;
29664
30089
  let slotLease;
29665
30090
  try {
@@ -29687,6 +30112,8 @@ var AgyAdapter = class AgyAdapter extends LlmAdapter {
29687
30112
  throw new LlmError("request aborted before the agy process was spawned", Err.AGY_ERROR);
29688
30113
  }
29689
30114
  if (account && account.dir) ensureIsolatedKeychain(account.dir);
30115
+ const runLogHome = agyHomeDirFor(account);
30116
+ const runLogsBefore = snapshotAgyLogs(runLogHome);
29690
30117
  const effectiveProxy = account?.proxyUrl || cfg.proxyUrl || detectSystemProxy() || void 0;
29691
30118
  const env = {
29692
30119
  ...process.env,
@@ -29707,7 +30134,20 @@ var AgyAdapter = class AgyAdapter extends LlmAdapter {
29707
30134
  const elapsed = Date.now() - lastSpawn;
29708
30135
  const jitter = Math.floor(Math.random() * 300);
29709
30136
  const targetInterval = this.minSpawnIntervalMs + jitter;
29710
- if (elapsed < targetInterval) await new Promise((r) => setTimeout(r, targetInterval - elapsed));
30137
+ if (elapsed < targetInterval) {
30138
+ if (options.signal?.aborted) throw new LlmError("request aborted before the agy process was spawned", Err.AGY_ERROR);
30139
+ await new Promise((r, reject) => {
30140
+ const timer = setTimeout(() => {
30141
+ options.signal?.removeEventListener("abort", onAbort);
30142
+ r();
30143
+ }, targetInterval - elapsed);
30144
+ const onAbort = () => {
30145
+ clearTimeout(timer);
30146
+ reject(new LlmError("request aborted before the agy process was spawned", Err.AGY_ERROR));
30147
+ };
30148
+ options.signal?.addEventListener("abort", onAbort, { once: true });
30149
+ });
30150
+ }
29711
30151
  this.lastAccountSpawnTime.set(account.id, Date.now());
29712
30152
  }
29713
30153
  proc = startAgyProcess({
@@ -29781,7 +30221,9 @@ var AgyAdapter = class AgyAdapter extends LlmAdapter {
29781
30221
  const consumable = r !== null && (r.ok || r.response !== "");
29782
30222
  const rawErrText = [outcome.stderrTail, parser.stats.lastResultError].filter(Boolean).join(" ");
29783
30223
  const isRateLimit = looksLikeRateLimit(rawErrText);
29784
- const transportFailure = looksLikeNetworkError([
30224
+ const runLogText = readAgyRunLogText(runLogHome, runLogsBefore);
30225
+ const keyringJitter = looksLikeAuthVerificationJitter(runLogText);
30226
+ const transportFailure = keyringJitter || looksLikeNetworkError([
29785
30227
  rawErrText,
29786
30228
  parser.stats.authFailureText ?? "",
29787
30229
  outcome.stderrTail,
@@ -29803,7 +30245,7 @@ var AgyAdapter = class AgyAdapter extends LlmAdapter {
29803
30245
  if (sawAuthFailure(parser, outcome)) failure = transportFailure ? {
29804
30246
  kind: "error",
29805
30247
  code: Err.NETWORK,
29806
- message: "agy could not validate its Google login over the network (transport failure, not a sign-out) — retrying on another account. " + (rawErrText !== "" ? brief(rawErrText) : "")
30248
+ message: "agy could not validate its Google login over the network (transport failure" + (keyringJitter ? "; its own log shows keyring auth timing out on the network, not a sign-out" : "") + ", not a sign-out) — retrying on another account. " + (rawErrText !== "" ? brief(rawErrText) : "")
29807
30249
  } : {
29808
30250
  kind: "error",
29809
30251
  code: Err.AUTH,
@@ -29871,7 +30313,11 @@ var AgyAdapter = class AgyAdapter extends LlmAdapter {
29871
30313
  if (account && silentTimeout) this.deps.pool?.advanceAccount(account.id, family);
29872
30314
  if (account && failure.code === Err.ELIGIBILITY) this.deps.pool?.advanceAccount(account.id, family);
29873
30315
  if (!outcome.aborted && failure.code !== Err.RATE_LIMIT && (failure.code === Err.NETWORK || failure.code === Err.OVERLOADED || failure.code === Err.AUTH) && account) this.deps.pool?.advanceAccount(account.id, family);
29874
- if (account && shouldMarkAuthRequired(failure.code, failure.message)) this.deps.pool?.markAuthRequired(account.id, failure.message);
30316
+ if (account && shouldMarkAuthRequired(failure.code, failure.message)) {
30317
+ const authoritative = isAuthoritativeAuthError(rawErrText + " " + runLogText);
30318
+ if (failure.code === Err.AUTH && !authoritative) this.deps.pool?.noteAuthFailure(account.id, failure.message);
30319
+ else this.deps.pool?.markAuthRequired(account.id, failure.message);
30320
+ }
29875
30321
  const staleConversation = failure.code === Err.AUTH || failure.message !== "" && /conversation.*(not found|invalid|not recognized|expired|does not exist)|session.*(expired|invalid)/i.test(failure.message);
29876
30322
  const interruptedTurn = failure.kind === "aborted" || outcome.timedOut === true;
29877
30323
  if (!isAux && sessionAccountKey !== "" && staleConversation) this.deps.store.delete(sessionAccountKey);
@@ -29903,7 +30349,7 @@ var AgyAdapter = class AgyAdapter extends LlmAdapter {
29903
30349
  message: "internal error: " + brief(String(err))
29904
30350
  });
29905
30351
  });
29906
- yield* this.driveSpan(rec, 0, !isAux && hasToolSupport, isCodeMode, goalContext, supportsGoalUpdate, supportsPlanExit);
30352
+ yield* this.driveSpan(rec, 0, !isAux && hasToolSupport, isCodeMode, goalContext, supportsGoalUpdate, supportsPlanExit, options.signal);
29907
30353
  }
29908
30354
  /**
29909
30355
  * Stream one span of a recording: map events from `from` until the mapper
@@ -29912,19 +30358,50 @@ var AgyAdapter = class AgyAdapter extends LlmAdapter {
29912
30358
  * as this span's terminal chunk — the turn ends exactly like a native
29913
30359
  * provider error.
29914
30360
  */
29915
- async *driveSpan(rec, from, cutOnTool, useCodeWrapper, goalContext, supportsGoalUpdate, supportsPlanExit) {
30361
+ async *driveSpan(rec, from, cutOnTool, useCodeWrapper, goalContext, supportsGoalUpdate, supportsPlanExit, signal) {
30362
+ if (signal?.aborted) {
30363
+ rec.requestAbort?.();
30364
+ yield {
30365
+ type: "finish",
30366
+ reason: {
30367
+ kind: "aborted",
30368
+ failure: {
30369
+ code: "ABORTED",
30370
+ message: "aborted by caller"
30371
+ }
30372
+ }
30373
+ };
30374
+ return;
30375
+ }
29916
30376
  this.deps.runs.remember(rec);
29917
30377
  this.deps.runs.pin(rec.runId);
29918
30378
  try {
29919
- yield* this.driveSpanBody(rec, from, cutOnTool, useCodeWrapper, goalContext, supportsGoalUpdate, supportsPlanExit);
30379
+ yield* this.driveSpanBody(rec, from, cutOnTool, useCodeWrapper, goalContext, supportsGoalUpdate, supportsPlanExit, signal);
29920
30380
  } finally {
29921
30381
  this.deps.runs.unpin(rec.runId);
29922
30382
  this.deps.runs.remember(rec);
29923
30383
  }
29924
30384
  }
29925
30385
  /** Body of {@link driveSpan}: map recorded events until the span finishes. */
29926
- async *driveSpanBody(rec, from, cutOnTool, useCodeWrapper, goalContext, supportsGoalUpdate, supportsPlanExit) {
30386
+ async *driveSpanBody(rec, from, cutOnTool, useCodeWrapper, goalContext, supportsGoalUpdate, supportsPlanExit, signal) {
29927
30387
  const queue = new ChunkQueue();
30388
+ let abortedLocally = false;
30389
+ const onAbort = () => {
30390
+ abortedLocally = true;
30391
+ rec.requestAbort?.();
30392
+ queue.push({
30393
+ type: "finish",
30394
+ reason: {
30395
+ kind: "aborted",
30396
+ failure: {
30397
+ code: "ABORTED",
30398
+ message: "aborted by caller"
30399
+ }
30400
+ }
30401
+ });
30402
+ queue.close();
30403
+ };
30404
+ signal?.addEventListener("abort", onAbort, { once: true });
29928
30405
  (async () => {
29929
30406
  const resolved = /* @__PURE__ */ new Map();
29930
30407
  const resolvedThoughts = /* @__PURE__ */ new Map();
@@ -29945,6 +30422,7 @@ var AgyAdapter = class AgyAdapter extends LlmAdapter {
29945
30422
  let i = from;
29946
30423
  try {
29947
30424
  for await (const ev of rec.eventsFrom(from)) {
30425
+ if (abortedLocally || signal?.aborted) break;
29948
30426
  const recordedThought = rec.getThoughts(i);
29949
30427
  if (recordedThought !== void 0) resolvedThoughts.set(i, recordedThought);
29950
30428
  else if (ev.kind === "step") {
@@ -30008,17 +30486,21 @@ var AgyAdapter = class AgyAdapter extends LlmAdapter {
30008
30486
  break;
30009
30487
  }
30010
30488
  }
30011
- if (!mapper.isFinished) {
30489
+ if (!mapper.isFinished && !abortedLocally && !signal?.aborted) {
30012
30490
  const f = rec.failureInfo;
30013
30491
  if (f !== null) for (const ch of mapper.emitFailure(f.kind, f.code, f.message)) queue.push(ch);
30014
30492
  else for (const ch of mapper.emitFailure("error", Err.INVALID_OUTPUT, "agy stream ended without a result event")) queue.push(ch);
30015
30493
  }
30016
30494
  } catch (err) {
30017
- for (const ch of mapper.emitFailure("error", Err.PROCESS_EXIT, "internal error: " + brief(String(err)))) queue.push(ch);
30495
+ if (!abortedLocally && !signal?.aborted) for (const ch of mapper.emitFailure("error", Err.PROCESS_EXIT, "internal error: " + brief(String(err)))) queue.push(ch);
30018
30496
  }
30019
30497
  queue.close();
30020
30498
  })();
30021
- yield* queue.drain();
30499
+ try {
30500
+ yield* queue.drain();
30501
+ } finally {
30502
+ signal?.removeEventListener("abort", onAbort);
30503
+ }
30022
30504
  }
30023
30505
  };
30024
30506
  /**
@@ -30721,6 +31203,8 @@ async function handle(deps, raw, invocation) {
30721
31203
  const qG = a.quotas.google?.remainingFraction !== void 0 ? `${Math.round(a.quotas.google.remainingFraction * 100)}%` : "unknown";
30722
31204
  const qC = a.quotas.anthropic?.remainingFraction !== void 0 ? `${Math.round(a.quotas.anthropic.remainingFraction * 100)}%` : "unknown";
30723
31205
  const qO = a.quotas.openai?.remainingFraction !== void 0 ? `${Math.round(a.quotas.openai.remainingFraction * 100)}%` : "unknown";
31206
+ const qA = a.quotas.argon?.weeklyFraction;
31207
+ const quotaLine = ` - Quota: Gemini ${qG} | Claude ${qC} | GPT-OSS ${qO}` + (qA !== void 0 ? ` | Argon ${Math.round(qA * 100)}% (weekly)` : "");
30724
31208
  const cds = [];
30725
31209
  for (const [fam, cd] of Object.entries(a.cooldowns)) if (cd && cd.cooldownUntil > Date.now()) {
30726
31210
  const sec = Math.ceil((cd.cooldownUntil - Date.now()) / 1e3);
@@ -30730,7 +31214,7 @@ async function handle(deps, raw, invocation) {
30730
31214
  if (!a.enabled) statusText = " - Status: ⏸️ Disabled";
30731
31215
  else if (a.authRequired) statusText = ` - Status: 🔴 Auth required (${a.authError || "sign in again"})`;
30732
31216
  else if (cds.length > 0) statusText = ` - ⚠️ ${cds.join("; ")}`;
30733
- lines.push(`${i + 1}. **${a.alias}** (${a.email || a.id})${isPrimary ? " ⭐ primary" : ""}`, ` - Proxy: ${a.proxyUrl ? redactProxyUrl(a.proxyUrl) : deps.cfg().proxyUrl ? redactProxyUrl(deps.cfg().proxyUrl) : "system default"}`, ` - Quota: Gemini ${qG} | Claude ${qC} | GPT-OSS ${qO}`, statusText);
31217
+ lines.push(`${i + 1}. **${a.alias}** (${a.email || a.id})${isPrimary ? " ⭐ primary" : ""}`, ` - Proxy: ${a.proxyUrl ? redactProxyUrl(a.proxyUrl) : deps.cfg().proxyUrl ? redactProxyUrl(deps.cfg().proxyUrl) : "system default"}`, quotaLine, statusText);
30734
31218
  }
30735
31219
  return ok(lines.join("\n"));
30736
31220
  }
@@ -31691,32 +32175,44 @@ var QuotaService = class {
31691
32175
  for (let i = 0; i < total; i++) ordered.push(AGY_ENDPOINTS[(this.preferredEndpointIndex + i) % total]);
31692
32176
  return ordered;
31693
32177
  }
32178
+ async fetchSummaryEndpoint(endpoint, accessToken, proxyUrl) {
32179
+ return agyFetch(`${endpoint}/v1internal:retrieveUserQuotaSummary`, {
32180
+ method: "POST",
32181
+ headers: {
32182
+ Authorization: `Bearer ${accessToken}`,
32183
+ "Content-Type": "application/json",
32184
+ "User-Agent": agyUserAgent()
32185
+ },
32186
+ body: JSON.stringify({})
32187
+ }, proxyUrl);
32188
+ }
31694
32189
  /**
31695
32190
  * Fetch official multi-bucket quota summary (both weekly and 5h limit windows)
31696
32191
  * via v1internal:retrieveUserQuotaSummary.
31697
32192
  */
31698
32193
  async fetchQuotaSummary(accessToken, proxyUrl) {
31699
32194
  const endpoints = this.getOrderedEndpoints();
32195
+ let partialSummary = null;
31700
32196
  for (let i = 0; i < endpoints.length; i++) {
31701
32197
  const endpoint = endpoints[i];
31702
32198
  try {
31703
- const res = await agyFetch(`${endpoint}/v1internal:retrieveUserQuotaSummary`, {
31704
- method: "POST",
31705
- headers: {
31706
- Authorization: `Bearer ${accessToken}`,
31707
- "Content-Type": "application/json",
31708
- "User-Agent": agyUserAgent()
31709
- },
31710
- body: JSON.stringify({})
31711
- }, proxyUrl);
32199
+ const res = await this.fetchSummaryEndpoint(endpoint, accessToken, proxyUrl);
31712
32200
  if (res.ok) {
31713
- this.preferredEndpointIndex = AGY_ENDPOINTS.indexOf(endpoint);
31714
- return await res.json();
32201
+ const summary = await res.json();
32202
+ if (summary?.groups?.some((g) => {
32203
+ const name = (g.displayName || "").toLowerCase();
32204
+ const desc = (g.description || "").toLowerCase();
32205
+ return name.includes("gemini") || desc.includes("gemini");
32206
+ })) {
32207
+ this.preferredEndpointIndex = AGY_ENDPOINTS.indexOf(endpoint);
32208
+ return summary;
32209
+ }
32210
+ if (!partialSummary && summary && Array.isArray(summary.groups) && summary.groups.length > 0) partialSummary = summary;
31715
32211
  }
31716
32212
  if (res.status === 401 || res.status === 403) break;
31717
32213
  } catch {}
31718
32214
  }
31719
- return null;
32215
+ return partialSummary;
31720
32216
  }
31721
32217
  /**
31722
32218
  * Fetch available models and model-level quotas via v1internal:fetchAvailableModels.
@@ -31795,9 +32291,10 @@ var QuotaService = class {
31795
32291
  if (summary && Array.isArray(summary.groups)) for (const group of summary.groups) {
31796
32292
  const dName = (group.displayName || "").toLowerCase();
31797
32293
  const desc = (group.description || "").toLowerCase();
31798
- const isGoogle = dName.includes("gemini") || desc.includes("gemini");
32294
+ const isArgon = dName.includes("argon") || desc.includes("argon");
32295
+ const isGoogle = !isArgon && (dName.includes("gemini") || desc.includes("gemini"));
31799
32296
  const is3P = dName.includes("claude") || dName.includes("gpt") || desc.includes("claude") || desc.includes("gpt");
31800
- const targetFamilies = isGoogle ? ["google"] : is3P ? ["anthropic", "openai"] : [];
32297
+ const targetFamilies = isArgon ? ["argon"] : isGoogle ? ["google"] : is3P ? ["anthropic", "openai"] : [];
31801
32298
  let fiveHourFrac;
31802
32299
  let fiveHourReset;
31803
32300
  let weeklyFrac;
@@ -32271,16 +32768,26 @@ var Semaphore = class {
32271
32768
  constructor(max) {
32272
32769
  this.max = max;
32273
32770
  }
32274
- async acquire() {
32771
+ async acquire(signal) {
32772
+ if (signal?.aborted) throw new Error("aborted before semaphore acquisition");
32275
32773
  if (this.active < Math.max(1, this.max())) {
32276
32774
  this.active++;
32277
32775
  return () => this.releaseOne();
32278
32776
  }
32279
- return new Promise((resolve) => {
32280
- this.queue.push(() => {
32777
+ return new Promise((resolve, reject) => {
32778
+ let entry;
32779
+ const onAbort = () => {
32780
+ const idx = this.queue.indexOf(entry);
32781
+ if (idx >= 0) this.queue.splice(idx, 1);
32782
+ reject(/* @__PURE__ */ new Error("aborted while waiting for semaphore slot"));
32783
+ };
32784
+ entry = () => {
32785
+ signal?.removeEventListener("abort", onAbort);
32281
32786
  this.active++;
32282
32787
  resolve(() => this.releaseOne());
32283
- });
32788
+ };
32789
+ signal?.addEventListener("abort", onAbort, { once: true });
32790
+ this.queue.push(entry);
32284
32791
  });
32285
32792
  }
32286
32793
  releaseOne() {
@@ -32355,8 +32862,14 @@ function apply(ctx, entryConfig = {}) {
32355
32862
  if (swept > 0) log("swept " + swept + " stale staging dir(s)");
32356
32863
  const logsSwept = pool.sweepOldLogs(getConfig().logRetentionDays);
32357
32864
  if (logsSwept > 0) log("swept " + logsSwept + " old log file(s)");
32865
+ if (process.platform === "darwin") try {
32866
+ if (repairMacSystemKeychain()) log("repaired macOS system keychain settings (restored real login keychain)");
32867
+ } catch {}
32358
32868
  let kcProvisioned = 0;
32359
- for (const acc of pool.getAccounts()) if (acc.dir && ensureIsolatedKeychain(acc.dir)) kcProvisioned++;
32869
+ for (const acc of pool.getAccounts()) if (acc.dir) {
32870
+ purgeStaleIsolatedKeychain(acc.dir);
32871
+ if (ensureIsolatedKeychain(acc.dir)) kcProvisioned++;
32872
+ }
32360
32873
  if (kcProvisioned > 0) log("provisioned isolated keychains for " + kcProvisioned + " pool account(s)");
32361
32874
  if (process.platform === "win32") {
32362
32875
  resolvePrimaryCredential(pool, {
@@ -32375,7 +32888,7 @@ function apply(ctx, entryConfig = {}) {
32375
32888
  store,
32376
32889
  pool,
32377
32890
  bin,
32378
- acquire: () => semaphore.acquire(),
32891
+ acquire: (signal) => semaphore.acquire(signal),
32379
32892
  log,
32380
32893
  runs,
32381
32894
  getMediaBaseUrl: () => webServerPort.current ? `http://127.0.0.1:${webServerPort.current}/plugins/agy-link/media` : null,
@@ -32799,19 +33312,7 @@ function apply(ctx, entryConfig = {}) {
32799
33312
  const alias = typeof body.alias === "string" ? body.alias : void 0;
32800
33313
  const acc = pool.createAccountSlot(alias);
32801
33314
  pool.markAuthRequired(acc.id, "not signed in yet — complete the terminal login");
32802
- if (body.autoOpenTerminal !== false && acc.dir) {
32803
- if (process.platform === "darwin") {
32804
- const script = `tell application "Terminal" to activate\ntell application "Terminal" to do script "export HOME='${acc.dir}'; agy"`;
32805
- execFile("osascript", ["-e", script], () => {});
32806
- } else if (process.platform === "win32") execFile("cmd.exe", [
32807
- "/c",
32808
- "start",
32809
- "cmd.exe",
32810
- "/k",
32811
- `set "HOME=${acc.dir}" && set "USERPROFILE=${acc.dir}" && agy`
32812
- ], { windowsHide: true }, () => {});
32813
- else execFile("x-terminal-emulator", ["-e", `sh -c "export HOME='${acc.dir}'; agy; exec sh"`], () => {});
32814
- }
33315
+ if (body.autoOpenTerminal !== false && acc.dir) openAccountTerminal(acc.dir);
32815
33316
  sendJson(res, 200, {
32816
33317
  ok: true,
32817
33318
  account: acc,
@@ -32836,17 +33337,7 @@ function apply(ctx, entryConfig = {}) {
32836
33337
  sendJson(res, 400, { error: "account has no isolated directory" });
32837
33338
  return;
32838
33339
  }
32839
- if (process.platform === "darwin") {
32840
- const script = `tell application "Terminal" to activate\ntell application "Terminal" to do script "export HOME='${acc.dir}'; agy"`;
32841
- execFile("osascript", ["-e", script], () => {});
32842
- } else if (process.platform === "win32") execFile("cmd.exe", [
32843
- "/c",
32844
- "start",
32845
- "cmd.exe",
32846
- "/k",
32847
- `set "HOME=${acc.dir}" && set "USERPROFILE=${acc.dir}" && agy`
32848
- ], { windowsHide: true }, () => {});
32849
- else execFile("x-terminal-emulator", ["-e", `sh -c "export HOME='${acc.dir}'; agy; exec sh"`], () => {});
33340
+ openAccountTerminal(acc.dir);
32850
33341
  sendJson(res, 200, {
32851
33342
  ok: true,
32852
33343
  dir: acc.dir