@synkro-sh/cli 1.3.26 → 1.3.28
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/bootstrap.js +60 -21
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -1677,7 +1677,7 @@ Commands:
|
|
|
1677
1677
|
status - print "running"/"stopped"
|
|
1678
1678
|
"""
|
|
1679
1679
|
|
|
1680
|
-
import os, sys, json, socket, time, signal, fcntl, re
|
|
1680
|
+
import os, sys, json, socket, time, signal, fcntl, re, select
|
|
1681
1681
|
import subprocess, threading
|
|
1682
1682
|
from pathlib import Path
|
|
1683
1683
|
|
|
@@ -1707,27 +1707,49 @@ def log(msg):
|
|
|
1707
1707
|
pass
|
|
1708
1708
|
|
|
1709
1709
|
|
|
1710
|
+
STALL_TIMEOUT_SEC = int(os.environ.get("SYNKRO_DAEMON_STALL_TIMEOUT", "10"))
|
|
1711
|
+
|
|
1710
1712
|
def _read_response(proc, timeout=45):
|
|
1711
1713
|
acc = []
|
|
1712
1714
|
deadline = time.time() + timeout
|
|
1715
|
+
last_data = time.time()
|
|
1716
|
+
fd = proc.stdout.fileno()
|
|
1717
|
+
buf = ""
|
|
1713
1718
|
while True:
|
|
1714
|
-
|
|
1719
|
+
remaining = deadline - time.time()
|
|
1720
|
+
if remaining <= 0:
|
|
1715
1721
|
log("read timeout")
|
|
1716
1722
|
return ""
|
|
1717
|
-
|
|
1718
|
-
|
|
1723
|
+
if time.time() - last_data > STALL_TIMEOUT_SEC:
|
|
1724
|
+
log(f"stall timeout: no data for {STALL_TIMEOUT_SEC}s")
|
|
1719
1725
|
return ""
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1726
|
+
ready, _, _ = select.select([fd], [], [], min(remaining, 2.0))
|
|
1727
|
+
if not ready:
|
|
1728
|
+
if proc.poll() is not None:
|
|
1729
|
+
log("process exited during read")
|
|
1730
|
+
return ""
|
|
1723
1731
|
continue
|
|
1724
|
-
|
|
1725
|
-
if
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1732
|
+
chunk = os.read(fd, 65536)
|
|
1733
|
+
if not chunk:
|
|
1734
|
+
return ""
|
|
1735
|
+
last_data = time.time()
|
|
1736
|
+
buf += chunk.decode("utf-8", errors="replace")
|
|
1737
|
+
while "\\n" in buf:
|
|
1738
|
+
line, buf = buf.split("\\n", 1)
|
|
1739
|
+
line = line.strip()
|
|
1740
|
+
if not line:
|
|
1741
|
+
continue
|
|
1742
|
+
try:
|
|
1743
|
+
obj = json.loads(line)
|
|
1744
|
+
except json.JSONDecodeError:
|
|
1745
|
+
continue
|
|
1746
|
+
t = obj.get("type")
|
|
1747
|
+
if t == "assistant":
|
|
1748
|
+
for c in obj.get("message", {}).get("content", []):
|
|
1749
|
+
if c.get("type") == "text":
|
|
1750
|
+
acc.append(c["text"])
|
|
1751
|
+
elif t == "result":
|
|
1752
|
+
return "".join(acc)
|
|
1731
1753
|
|
|
1732
1754
|
|
|
1733
1755
|
def _send_msg(proc, text):
|
|
@@ -1757,6 +1779,7 @@ class WarmGrader:
|
|
|
1757
1779
|
self._warm_thread = None
|
|
1758
1780
|
self._lock = threading.Lock()
|
|
1759
1781
|
self._total_grades = 0
|
|
1782
|
+
self._prewarm_ok = True
|
|
1760
1783
|
self._start_prewarm()
|
|
1761
1784
|
|
|
1762
1785
|
def _make_proc(self):
|
|
@@ -1786,27 +1809,32 @@ class WarmGrader:
|
|
|
1786
1809
|
log("pre-warming process")
|
|
1787
1810
|
proc = self._make_proc()
|
|
1788
1811
|
_send_msg(proc, "Ready")
|
|
1789
|
-
resp = _read_response(proc, timeout=
|
|
1812
|
+
resp = _read_response(proc, timeout=15)
|
|
1790
1813
|
if resp:
|
|
1791
1814
|
with self._lock:
|
|
1792
1815
|
old = self._warm_proc
|
|
1793
1816
|
self._warm_proc = proc
|
|
1817
|
+
self._prewarm_ok = True
|
|
1794
1818
|
if old:
|
|
1795
1819
|
self._kill_proc(old)
|
|
1796
1820
|
log(f"pre-warm ready ({len(resp)} chars)")
|
|
1797
1821
|
else:
|
|
1798
1822
|
log("pre-warm response empty")
|
|
1799
1823
|
self._kill_proc(proc)
|
|
1824
|
+
self._prewarm_ok = False
|
|
1800
1825
|
except Exception as e:
|
|
1801
1826
|
log(f"pre-warm failed: {e}")
|
|
1827
|
+
self._prewarm_ok = False
|
|
1802
1828
|
|
|
1803
1829
|
def _start_prewarm(self):
|
|
1804
1830
|
self._warm_thread = threading.Thread(target=self._prewarm, daemon=True)
|
|
1805
1831
|
self._warm_thread.start()
|
|
1806
1832
|
|
|
1807
1833
|
def grade(self, prompt):
|
|
1808
|
-
if self._warm_thread:
|
|
1809
|
-
self._warm_thread.join(timeout=
|
|
1834
|
+
if self._warm_thread and self._prewarm_ok:
|
|
1835
|
+
self._warm_thread.join(timeout=8)
|
|
1836
|
+
elif self._warm_thread and not self._prewarm_ok:
|
|
1837
|
+
log("skipping prewarm join (last prewarm failed)")
|
|
1810
1838
|
|
|
1811
1839
|
with self._lock:
|
|
1812
1840
|
proc = self._warm_proc
|
|
@@ -1817,11 +1845,17 @@ class WarmGrader:
|
|
|
1817
1845
|
log("no warm process, cold fallback")
|
|
1818
1846
|
proc = self._make_proc()
|
|
1819
1847
|
warm = False
|
|
1848
|
+
elif proc.stdin.closed:
|
|
1849
|
+
log("warm process stdin closed, cold fallback")
|
|
1850
|
+
self._kill_proc(proc)
|
|
1851
|
+
proc = self._make_proc()
|
|
1852
|
+
warm = False
|
|
1820
1853
|
|
|
1854
|
+
wall_limit = int(os.environ.get("SYNKRO_DAEMON_WALL_TIMEOUT", "12"))
|
|
1821
1855
|
t0 = time.time()
|
|
1822
1856
|
try:
|
|
1823
1857
|
_send_msg(proc, prompt)
|
|
1824
|
-
resp = _read_response(proc, timeout=GRADE_TIMEOUT_SEC)
|
|
1858
|
+
resp = _read_response(proc, timeout=min(GRADE_TIMEOUT_SEC, wall_limit))
|
|
1825
1859
|
except Exception as e:
|
|
1826
1860
|
log(f"grade error: {e}")
|
|
1827
1861
|
resp = ""
|
|
@@ -2339,7 +2373,7 @@ async function refreshToken() {
|
|
|
2339
2373
|
const creds = loadCredentials();
|
|
2340
2374
|
if (!creds?.refresh_token) return false;
|
|
2341
2375
|
try {
|
|
2342
|
-
const response = await fetch(`${
|
|
2376
|
+
const response = await fetch(`${SYNKRO_API_URL}/api/auth/refresh`, {
|
|
2343
2377
|
method: "POST",
|
|
2344
2378
|
headers: { "Content-Type": "application/json" },
|
|
2345
2379
|
body: JSON.stringify({ refresh_token: creds.refresh_token })
|
|
@@ -2348,6 +2382,7 @@ async function refreshToken() {
|
|
|
2348
2382
|
const data = await response.json();
|
|
2349
2383
|
if (data.access_token) {
|
|
2350
2384
|
saveCredentials({
|
|
2385
|
+
...creds,
|
|
2351
2386
|
access_token: data.access_token,
|
|
2352
2387
|
refresh_token: data.refresh_token || creds.refresh_token
|
|
2353
2388
|
});
|
|
@@ -2379,7 +2414,7 @@ function clearCredentials() {
|
|
|
2379
2414
|
unlinkSync2(AUTH_FILE);
|
|
2380
2415
|
}
|
|
2381
2416
|
}
|
|
2382
|
-
var PORT, RAW_WEB_AUTH_URL, SYNKRO_WEB_AUTH_URL, AUTH_FILE, ERROR_HTML, refreshPromise;
|
|
2417
|
+
var PORT, RAW_WEB_AUTH_URL, SYNKRO_WEB_AUTH_URL, AUTH_FILE, RAW_API_URL, SYNKRO_API_URL, ERROR_HTML, refreshPromise;
|
|
2383
2418
|
var init_stub = __esm({
|
|
2384
2419
|
"cli/auth/stub.ts"() {
|
|
2385
2420
|
"use strict";
|
|
@@ -2387,6 +2422,8 @@ var init_stub = __esm({
|
|
|
2387
2422
|
RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;
|
|
2388
2423
|
SYNKRO_WEB_AUTH_URL = RAW_WEB_AUTH_URL && /^https?:\/\//.test(RAW_WEB_AUTH_URL) ? RAW_WEB_AUTH_URL : "https://app.synkro.sh";
|
|
2389
2424
|
AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join3(homedir3(), ".synkro", "credentials.json");
|
|
2425
|
+
RAW_API_URL = process.env.SYNKRO_CRUD_URL || process.env.SYNKRO_API_URL;
|
|
2426
|
+
SYNKRO_API_URL = RAW_API_URL && /^https?:\/\//.test(RAW_API_URL) ? RAW_API_URL : "https://api.synkro.sh";
|
|
2390
2427
|
ERROR_HTML = `
|
|
2391
2428
|
<!DOCTYPE html>
|
|
2392
2429
|
<html>
|
|
@@ -2448,6 +2485,7 @@ async function callApi(method, endpoint, body) {
|
|
|
2448
2485
|
throw new Error("API URL not configured. Run `synkro install` first.");
|
|
2449
2486
|
}
|
|
2450
2487
|
const url = `${API_URL}${endpoint}`;
|
|
2488
|
+
await ensureValidToken();
|
|
2451
2489
|
const accessToken = getAccessToken();
|
|
2452
2490
|
const headers = {
|
|
2453
2491
|
"Content-Type": "application/json"
|
|
@@ -3333,7 +3371,7 @@ function writeConfigEnv(opts) {
|
|
|
3333
3371
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
3334
3372
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
3335
3373
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
3336
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.3.
|
|
3374
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.3.28")}`
|
|
3337
3375
|
];
|
|
3338
3376
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
3339
3377
|
if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
|
|
@@ -4000,6 +4038,7 @@ async function statusCommand() {
|
|
|
4000
4038
|
const gatewayUrl = (config.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/^['"]|['"]$/g, "");
|
|
4001
4039
|
let serverTier = config.SYNKRO_TIER || "(unset)";
|
|
4002
4040
|
let serverInference = config.SYNKRO_INFERENCE || "fast";
|
|
4041
|
+
await ensureValidToken();
|
|
4003
4042
|
const token = getAccessToken();
|
|
4004
4043
|
if (token) {
|
|
4005
4044
|
try {
|