@synkro-sh/cli 1.1.6 → 1.1.7
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 +89 -35
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -1335,7 +1335,7 @@ returns the assistant's response text. ONE CC startup (~3.5s) amortizes
|
|
|
1335
1335
|
across N gradings.
|
|
1336
1336
|
|
|
1337
1337
|
Session bloat is bounded: the daemon rotates its claude subprocess every
|
|
1338
|
-
ROTATION_CALLS (default
|
|
1338
|
+
ROTATION_CALLS (default 10) gradings or ROTATION_AGE_SEC (default 1h),
|
|
1339
1339
|
whichever comes first. Each rotation eats a one-time ~5s primer cost; calls
|
|
1340
1340
|
in between target ~2-3s steady-state.
|
|
1341
1341
|
|
|
@@ -1365,9 +1365,9 @@ def mode_paths(mode):
|
|
|
1365
1365
|
MODE = "edit"
|
|
1366
1366
|
PID_FILE, SOCK_PATH, LOG_FILE = mode_paths(MODE)
|
|
1367
1367
|
|
|
1368
|
-
ROTATION_CALLS = int(os.environ.get("SYNKRO_DAEMON_ROTATE_CALLS", "
|
|
1368
|
+
ROTATION_CALLS = int(os.environ.get("SYNKRO_DAEMON_ROTATE_CALLS", "10"))
|
|
1369
1369
|
ROTATION_AGE_SEC = int(os.environ.get("SYNKRO_DAEMON_ROTATE_AGE", "3600"))
|
|
1370
|
-
GRADE_TIMEOUT_SEC =
|
|
1370
|
+
GRADE_TIMEOUT_SEC = int(os.environ.get("SYNKRO_DAEMON_GRADE_TIMEOUT", "10"))
|
|
1371
1371
|
DEFAULT_MODEL = os.environ.get("SYNKRO_DAEMON_MODEL", "claude-sonnet-4-6")
|
|
1372
1372
|
MAX_PROMPT_BYTES = 4 * 1024 * 1024
|
|
1373
1373
|
|
|
@@ -1380,6 +1380,8 @@ def log(msg):
|
|
|
1380
1380
|
pass
|
|
1381
1381
|
|
|
1382
1382
|
|
|
1383
|
+
PREWARM_HEADROOM = 4
|
|
1384
|
+
|
|
1383
1385
|
class GraderDaemon:
|
|
1384
1386
|
def __init__(self, primer):
|
|
1385
1387
|
self.primer = primer or ""
|
|
@@ -1387,14 +1389,12 @@ class GraderDaemon:
|
|
|
1387
1389
|
self.calls = 0
|
|
1388
1390
|
self.start_time = 0.0
|
|
1389
1391
|
self.lock = threading.Lock()
|
|
1392
|
+
self._next_proc = None
|
|
1393
|
+
self._prewarm_thread = None
|
|
1390
1394
|
self._spawn()
|
|
1391
1395
|
|
|
1392
|
-
def
|
|
1393
|
-
|
|
1394
|
-
try: self.proc.terminate(); self.proc.wait(timeout=3)
|
|
1395
|
-
except Exception: self.proc.kill()
|
|
1396
|
-
log("spawning claude subprocess")
|
|
1397
|
-
self.proc = subprocess.Popen(
|
|
1396
|
+
def _make_proc(self):
|
|
1397
|
+
return subprocess.Popen(
|
|
1398
1398
|
[
|
|
1399
1399
|
"claude", "--print", "--model", DEFAULT_MODEL,
|
|
1400
1400
|
"--input-format=stream-json",
|
|
@@ -1405,41 +1405,26 @@ class GraderDaemon:
|
|
|
1405
1405
|
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
|
1406
1406
|
stderr=subprocess.DEVNULL, text=True, bufsize=1,
|
|
1407
1407
|
)
|
|
1408
|
-
if self.primer:
|
|
1409
|
-
self._send(self.primer)
|
|
1410
|
-
primer_resp = self._recv()
|
|
1411
|
-
log(f"primer ack: {primer_resp[:80]!r}")
|
|
1412
|
-
self.calls = 0
|
|
1413
|
-
self.start_time = time.time()
|
|
1414
1408
|
|
|
1415
|
-
def
|
|
1409
|
+
def _send_to(self, proc, text):
|
|
1416
1410
|
msg = json.dumps({
|
|
1417
1411
|
"type": "user",
|
|
1418
1412
|
"message": {"role": "user", "content": [{"type": "text", "text": text}]},
|
|
1419
1413
|
"parent_tool_use_id": None,
|
|
1420
1414
|
"session_id": "",
|
|
1421
1415
|
})
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
self.proc.stdin.flush()
|
|
1425
|
-
except (BrokenPipeError, OSError) as e:
|
|
1426
|
-
log(f"send broke: {e}; respawn")
|
|
1427
|
-
self._spawn()
|
|
1428
|
-
self.proc.stdin.write(msg + "\\n")
|
|
1429
|
-
self.proc.stdin.flush()
|
|
1416
|
+
proc.stdin.write(msg + "\\n")
|
|
1417
|
+
proc.stdin.flush()
|
|
1430
1418
|
|
|
1431
|
-
def
|
|
1419
|
+
def _recv_from(self, proc):
|
|
1432
1420
|
acc = []
|
|
1433
1421
|
deadline = time.time() + GRADE_TIMEOUT_SEC
|
|
1434
1422
|
while True:
|
|
1435
1423
|
if time.time() > deadline:
|
|
1436
|
-
log("recv timeout
|
|
1437
|
-
self._spawn()
|
|
1424
|
+
log("recv timeout")
|
|
1438
1425
|
return ""
|
|
1439
|
-
line =
|
|
1426
|
+
line = proc.stdout.readline()
|
|
1440
1427
|
if not line:
|
|
1441
|
-
log("subprocess closed stdout; respawn")
|
|
1442
|
-
self._spawn()
|
|
1443
1428
|
return ""
|
|
1444
1429
|
try:
|
|
1445
1430
|
obj = json.loads(line)
|
|
@@ -1453,18 +1438,87 @@ class GraderDaemon:
|
|
|
1453
1438
|
elif t == "result":
|
|
1454
1439
|
return "".join(acc)
|
|
1455
1440
|
|
|
1441
|
+
def _spawn(self):
|
|
1442
|
+
if self.proc and self.proc.poll() is None:
|
|
1443
|
+
try: self.proc.terminate(); self.proc.wait(timeout=3)
|
|
1444
|
+
except Exception: self.proc.kill()
|
|
1445
|
+
log("spawning claude subprocess")
|
|
1446
|
+
self.proc = self._make_proc()
|
|
1447
|
+
if self.primer:
|
|
1448
|
+
self._send_to(self.proc, self.primer)
|
|
1449
|
+
primer_resp = self._recv_from(self.proc)
|
|
1450
|
+
log(f"primer ack: {primer_resp[:80]!r}")
|
|
1451
|
+
self.calls = 0
|
|
1452
|
+
self.start_time = time.time()
|
|
1453
|
+
self._next_proc = None
|
|
1454
|
+
|
|
1455
|
+
def _prewarm_worker(self):
|
|
1456
|
+
try:
|
|
1457
|
+
log("pre-warming next subprocess")
|
|
1458
|
+
proc = self._make_proc()
|
|
1459
|
+
if self.primer:
|
|
1460
|
+
self._send_to(proc, self.primer)
|
|
1461
|
+
resp = self._recv_from(proc)
|
|
1462
|
+
log(f"pre-warm ack: {resp[:80]!r}")
|
|
1463
|
+
self._next_proc = proc
|
|
1464
|
+
log("pre-warm ready")
|
|
1465
|
+
except Exception as e:
|
|
1466
|
+
log(f"pre-warm failed: {e}")
|
|
1467
|
+
self._next_proc = None
|
|
1468
|
+
|
|
1469
|
+
def _maybe_prewarm(self):
|
|
1470
|
+
if self._prewarm_thread and self._prewarm_thread.is_alive():
|
|
1471
|
+
return
|
|
1472
|
+
if self._next_proc is not None:
|
|
1473
|
+
return
|
|
1474
|
+
if self.calls >= ROTATION_CALLS - PREWARM_HEADROOM:
|
|
1475
|
+
self._prewarm_thread = threading.Thread(target=self._prewarm_worker, daemon=True)
|
|
1476
|
+
self._prewarm_thread.start()
|
|
1477
|
+
|
|
1478
|
+
def _try_rotate(self):
|
|
1479
|
+
if self._next_proc and self._next_proc.poll() is None:
|
|
1480
|
+
log("hot-swapping to pre-warmed subprocess")
|
|
1481
|
+
old = self.proc
|
|
1482
|
+
self.proc = self._next_proc
|
|
1483
|
+
self._next_proc = None
|
|
1484
|
+
self._prewarm_thread = None
|
|
1485
|
+
self.calls = 0
|
|
1486
|
+
self.start_time = time.time()
|
|
1487
|
+
if old and old.poll() is None:
|
|
1488
|
+
threading.Thread(target=lambda: (old.terminate(), old.wait()), daemon=True).start()
|
|
1489
|
+
return True
|
|
1490
|
+
log("pre-warm not ready, deferring rotation")
|
|
1491
|
+
return False
|
|
1492
|
+
|
|
1493
|
+
def _send(self, text):
|
|
1494
|
+
try:
|
|
1495
|
+
self._send_to(self.proc, text)
|
|
1496
|
+
except (BrokenPipeError, OSError) as e:
|
|
1497
|
+
log(f"send broke: {e}; respawn")
|
|
1498
|
+
self._spawn()
|
|
1499
|
+
self._send_to(self.proc, text)
|
|
1500
|
+
|
|
1501
|
+
def _recv(self):
|
|
1502
|
+
resp = self._recv_from(self.proc)
|
|
1503
|
+
if not resp and (not self.proc or self.proc.poll() is not None):
|
|
1504
|
+
log("subprocess died; respawn")
|
|
1505
|
+
self._spawn()
|
|
1506
|
+
return resp
|
|
1507
|
+
|
|
1456
1508
|
def grade(self, prompt):
|
|
1457
1509
|
with self.lock:
|
|
1458
|
-
age = time.time() - self.start_time
|
|
1459
|
-
if self.calls >= ROTATION_CALLS or age >= ROTATION_AGE_SEC:
|
|
1460
|
-
log(f"rotating: calls={self.calls} age={age:.0f}s")
|
|
1461
|
-
self._spawn()
|
|
1462
1510
|
t0 = time.time()
|
|
1463
1511
|
self._send(prompt)
|
|
1464
1512
|
resp = self._recv()
|
|
1465
1513
|
elapsed = (time.time() - t0) * 1000
|
|
1466
1514
|
self.calls += 1
|
|
1467
1515
|
log(f"grade #{self.calls} elapsed={elapsed:.0f}ms resp_chars={len(resp)}")
|
|
1516
|
+
age = time.time() - self.start_time
|
|
1517
|
+
if self.calls >= ROTATION_CALLS or age >= ROTATION_AGE_SEC:
|
|
1518
|
+
if not self._try_rotate():
|
|
1519
|
+
self._maybe_prewarm()
|
|
1520
|
+
else:
|
|
1521
|
+
self._maybe_prewarm()
|
|
1468
1522
|
return resp
|
|
1469
1523
|
|
|
1470
1524
|
|
|
@@ -2090,7 +2144,7 @@ function writeConfigEnv(opts) {
|
|
|
2090
2144
|
`SYNKRO_GATEWAY_URL=${shellQuoteSingle(safeGateway)}`,
|
|
2091
2145
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
2092
2146
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
2093
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.1.
|
|
2147
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.1.7")}`
|
|
2094
2148
|
];
|
|
2095
2149
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
2096
2150
|
if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
|