@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 +122 -5
- package/dist/bin.js.map +1 -1
- package/package.json +4 -4
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 ${
|
|
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 (
|
|
2544
|
-
|
|
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:
|
|
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
|
}
|