@appchy/jarvis 0.1.6 → 0.1.8

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/bin.js CHANGED
@@ -2519,14 +2519,77 @@ function createUpstreamClient(config) {
2519
2519
  let ws = null;
2520
2520
  let messageHandler = null;
2521
2521
  let reconnectTimer = null;
2522
+ let refreshTimer = null;
2522
2523
  let closed = false;
2524
+ let authFailures = 0;
2525
+ let retries = 0;
2526
+ let currentToken = config.token;
2527
+ const MAX_AUTH_RETRIES = 3;
2528
+ const MAX_RETRIES = 3;
2529
+ const REFRESH_BUFFER_MS = 5 * 60 * 1e3;
2530
+ function getRefreshUrl() {
2531
+ if (config.refreshUrl) return config.refreshUrl;
2532
+ if (!config.refreshToken) return null;
2533
+ return config.apiUrl.replace(/^wss:/, "https:").replace(/^ws:/, "http:") + "/refresh";
2534
+ }
2535
+ function getTokenExpiry(token) {
2536
+ try {
2537
+ const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString());
2538
+ return payload.exp ?? null;
2539
+ } catch {
2540
+ return null;
2541
+ }
2542
+ }
2543
+ function scheduleRefresh() {
2544
+ if (refreshTimer) clearTimeout(refreshTimer);
2545
+ if (!config.refreshToken) return;
2546
+ const exp = getTokenExpiry(currentToken);
2547
+ if (!exp) return;
2548
+ const msUntilExpiry = exp * 1e3 - Date.now();
2549
+ const refreshIn = Math.max(msUntilExpiry - REFRESH_BUFFER_MS, 0);
2550
+ logger.sys.info("[Upstream] Token refresh scheduled", {
2551
+ expiresIn: `${Math.round(msUntilExpiry / 1e3)}s`,
2552
+ refreshIn: `${Math.round(refreshIn / 1e3)}s`
2553
+ });
2554
+ refreshTimer = setTimeout(refreshToken, refreshIn);
2555
+ }
2556
+ async function refreshToken() {
2557
+ const url = getRefreshUrl();
2558
+ if (!url || !config.refreshToken) return false;
2559
+ try {
2560
+ logger.sys.info("[Upstream] Refreshing token...");
2561
+ const res = await fetch(url, {
2562
+ method: "POST",
2563
+ headers: { "Content-Type": "application/json" },
2564
+ body: JSON.stringify({ refreshToken: config.refreshToken })
2565
+ });
2566
+ if (!res.ok) {
2567
+ logger.sys.error("[Upstream] Token refresh failed", { status: res.status });
2568
+ return false;
2569
+ }
2570
+ const { token } = await res.json();
2571
+ currentToken = token;
2572
+ logger.sys.info("[Upstream] Token refreshed successfully");
2573
+ ws?.close();
2574
+ scheduleRefresh();
2575
+ return true;
2576
+ } catch (err) {
2577
+ logger.sys.error("[Upstream] Token refresh error", {
2578
+ error: err instanceof Error ? err.message : String(err)
2579
+ });
2580
+ return false;
2581
+ }
2582
+ }
2523
2583
  function connect() {
2524
2584
  if (closed) return;
2525
2585
  ws = new WebSocket2(config.apiUrl, {
2526
- headers: { authorization: `Bearer ${config.token}` }
2586
+ headers: { authorization: `Bearer ${currentToken}` }
2527
2587
  });
2528
2588
  ws.on("open", () => {
2589
+ authFailures = 0;
2590
+ retries = 0;
2529
2591
  logger.sys.info("[Upstream] Connected to cloud", { apiUrl: config.apiUrl });
2592
+ scheduleRefresh();
2530
2593
  });
2531
2594
  ws.on("message", (raw) => {
2532
2595
  try {
@@ -2539,11 +2602,56 @@ function createUpstreamClient(config) {
2539
2602
  } catch {
2540
2603
  }
2541
2604
  });
2542
- ws.on("close", () => {
2543
- if (!closed) {
2544
- logger.sys.info("[Upstream] Disconnected, reconnecting in 3s...");
2605
+ ws.on("close", (code) => {
2606
+ if (closed) return;
2607
+ if (code === 4001 || code === 4003) {
2608
+ if (config.refreshToken) {
2609
+ refreshToken().then((ok) => {
2610
+ if (ok) {
2611
+ reconnectTimer = setTimeout(connect, 1e3);
2612
+ } else {
2613
+ authFailures++;
2614
+ if (authFailures >= MAX_AUTH_RETRIES) {
2615
+ logger.sys.error("[Upstream] Auth failed after max retries, giving up", {
2616
+ code,
2617
+ attempts: authFailures
2618
+ });
2619
+ return;
2620
+ }
2621
+ reconnectTimer = setTimeout(connect, 3e3);
2622
+ }
2623
+ });
2624
+ return;
2625
+ }
2626
+ authFailures++;
2627
+ if (authFailures >= MAX_AUTH_RETRIES) {
2628
+ logger.sys.error("[Upstream] Auth failed after max retries, giving up", {
2629
+ code,
2630
+ attempts: authFailures
2631
+ });
2632
+ return;
2633
+ }
2634
+ logger.sys.warn("[Upstream] Auth failed, retrying...", {
2635
+ code,
2636
+ attempt: authFailures,
2637
+ maxRetries: MAX_AUTH_RETRIES
2638
+ });
2545
2639
  reconnectTimer = setTimeout(connect, 3e3);
2640
+ return;
2641
+ }
2642
+ retries++;
2643
+ if (retries >= MAX_RETRIES) {
2644
+ logger.sys.error("[Upstream] Max reconnect attempts reached, giving up", {
2645
+ attempts: retries
2646
+ });
2647
+ return;
2546
2648
  }
2649
+ logger.sys.info("[Upstream] Disconnected, reconnecting in 3s...", {
2650
+ code,
2651
+ attempt: retries,
2652
+ maxRetries: MAX_RETRIES
2653
+ });
2654
+ reconnectTimer = setTimeout(connect, 3e3);
2547
2655
  });
2548
2656
  ws.on("error", (err) => {
2549
2657
  logger.sys.error("[Upstream] Connection error", {
@@ -2565,6 +2673,7 @@ function createUpstreamClient(config) {
2565
2673
  function close() {
2566
2674
  closed = true;
2567
2675
  if (reconnectTimer) clearTimeout(reconnectTimer);
2676
+ if (refreshTimer) clearTimeout(refreshTimer);
2568
2677
  ws?.close();
2569
2678
  }
2570
2679
  return { connect, send, onMessage, isConnected, close };
@@ -4705,6 +4814,7 @@ function createCli() {
4705
4814
  ...existing,
4706
4815
  apiUrl: parsed.apiUrl,
4707
4816
  token: parsed.jwt,
4817
+ refreshToken: parsed.refreshToken,
4708
4818
  userId: parsed.userId,
4709
4819
  envId: parsed.envId,
4710
4820
  ...opts.workspace ? { workspacePath: opts.workspace } : {},
@@ -4744,7 +4854,14 @@ function createCli() {
4744
4854
  anthropicApiKey,
4745
4855
  useSubscription: !anthropicApiKey,
4746
4856
  userId,
4747
- upstream: opts.upstream && config?.apiUrl && config?.token ? { apiUrl: config.apiUrl, token: config.token } : void 0
4857
+ upstream: (() => {
4858
+ const apiUrl = process.env.JARVIS_UPSTREAM_URL ?? config?.apiUrl;
4859
+ const token = process.env.JARVIS_UPSTREAM_TOKEN ?? config?.token;
4860
+ if (opts.upstream && apiUrl && token) {
4861
+ return { apiUrl, token, refreshToken: config?.refreshToken };
4862
+ }
4863
+ return void 0;
4864
+ })()
4748
4865
  });
4749
4866
  return;
4750
4867
  }