@synkro-sh/cli 1.3.41 → 1.3.42

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 CHANGED
@@ -1988,7 +1988,7 @@ Commands:
1988
1988
  status - print "running"/"stopped"
1989
1989
  """
1990
1990
 
1991
- import os, sys, json, socket, time, signal, fcntl, re, select
1991
+ import os, sys, json, socket, time, signal, fcntl, re, select, queue
1992
1992
  import subprocess, threading, urllib.request, urllib.error
1993
1993
  from pathlib import Path
1994
1994
 
@@ -2119,13 +2119,18 @@ def log(msg):
2119
2119
 
2120
2120
  STALL_TIMEOUT_SEC = int(os.environ.get("SYNKRO_DAEMON_STALL_TIMEOUT", "10"))
2121
2121
 
2122
- def _read_response(proc, timeout=45):
2122
+ def _read_response(proc, timeout=45, stop_event=None):
2123
+ """Read stream-json from proc.stdout until a 'result' message arrives.
2124
+ If stop_event is set externally, returns immediately with empty string \u2014
2125
+ used by the parallel-race grade path to abort the loser."""
2123
2126
  acc = []
2124
2127
  deadline = time.time() + timeout
2125
2128
  last_data = time.time()
2126
2129
  fd = proc.stdout.fileno()
2127
2130
  buf = ""
2128
2131
  while True:
2132
+ if stop_event is not None and stop_event.is_set():
2133
+ return ""
2129
2134
  remaining = deadline - time.time()
2130
2135
  if remaining <= 0:
2131
2136
  log("read timeout")
@@ -2133,7 +2138,7 @@ def _read_response(proc, timeout=45):
2133
2138
  if time.time() - last_data > STALL_TIMEOUT_SEC:
2134
2139
  log(f"stall timeout: no data for {STALL_TIMEOUT_SEC}s")
2135
2140
  return ""
2136
- ready, _, _ = select.select([fd], [], [], min(remaining, 2.0))
2141
+ ready, _, _ = select.select([fd], [], [], min(remaining, 1.0))
2137
2142
  if not ready:
2138
2143
  if proc.poll() is not None:
2139
2144
  log("process exited during read")
@@ -2262,39 +2267,71 @@ class WarmGrader:
2262
2267
  self._warm_proc = None
2263
2268
  self._warm_ready_at = 0.0
2264
2269
 
2265
- # Discard warm processes that have been idle too long.
2266
2270
  WARM_TTL_SEC = int(os.environ.get("SYNKRO_DAEMON_WARM_TTL", "15"))
2267
- warm = True
2268
- if not proc or proc.poll() is not None:
2269
- log("no warm process, cold fallback")
2270
- proc = self._make_proc()
2271
- warm = False
2272
- elif proc.stdin.closed:
2273
- log("warm process stdin closed, cold fallback")
2271
+ # Decide if the warm process is usable for the race.
2272
+ warm_usable = bool(proc) and proc.poll() is None and not proc.stdin.closed
2273
+ if warm_usable and ready_at and (time.time() - ready_at) > WARM_TTL_SEC:
2274
2274
  self._kill_proc(proc)
2275
- proc = self._make_proc()
2276
- warm = False
2277
- elif ready_at and (time.time() - ready_at) > WARM_TTL_SEC:
2278
- age = time.time() - ready_at
2279
- log(f"warm process stale ({age:.0f}s > {WARM_TTL_SEC}s), cold fallback")
2275
+ warm_usable = False
2276
+ if not warm_usable and proc:
2280
2277
  self._kill_proc(proc)
2281
- proc = self._make_proc()
2282
- warm = False
2278
+ proc = None
2283
2279
 
2284
2280
  wall_limit = int(os.environ.get("SYNKRO_DAEMON_WALL_TIMEOUT", "12"))
2281
+ race_timeout = min(GRADE_TIMEOUT_SEC, wall_limit)
2282
+
2283
+ # Race a warm and a fresh cold process. Whichever returns a non-empty
2284
+ # response first wins; the other is killed. If we have no warm, just
2285
+ # run cold solo (no benefit to racing two cold spawns of the same age).
2286
+ cold_proc = self._make_proc() if warm_usable else proc or self._make_proc()
2287
+ warm_proc = proc if warm_usable else None
2288
+
2289
+ result_q = queue.Queue()
2290
+ stop_event = threading.Event()
2291
+
2292
+ def grade_worker(p, label):
2293
+ try:
2294
+ _send_msg(p, prompt)
2295
+ r = _read_response(p, timeout=race_timeout, stop_event=stop_event)
2296
+ if r:
2297
+ result_q.put((label, r))
2298
+ except Exception as e:
2299
+ log(f"grade {label} error: {e}")
2300
+
2301
+ threads = []
2302
+ if warm_proc is not None:
2303
+ t = threading.Thread(target=grade_worker, args=(warm_proc, "warm"), daemon=True)
2304
+ t.start()
2305
+ threads.append(t)
2306
+ t = threading.Thread(target=grade_worker, args=(cold_proc, "cold"), daemon=True)
2307
+ t.start()
2308
+ threads.append(t)
2309
+
2285
2310
  t0 = time.time()
2311
+ winner_label = None
2312
+ resp = ""
2286
2313
  try:
2287
- _send_msg(proc, prompt)
2288
- resp = _read_response(proc, timeout=min(GRADE_TIMEOUT_SEC, wall_limit))
2289
- except Exception as e:
2290
- log(f"grade error: {e}")
2291
- resp = ""
2314
+ deadline = time.time() + race_timeout + 2
2315
+ while time.time() < deadline:
2316
+ try:
2317
+ label, r = result_q.get(timeout=0.5)
2318
+ if r:
2319
+ resp = r
2320
+ winner_label = label
2321
+ break
2322
+ except queue.Empty:
2323
+ if all(not th.is_alive() for th in threads):
2324
+ break
2292
2325
  finally:
2293
- self._kill_proc(proc)
2326
+ stop_event.set()
2327
+ if warm_proc is not None:
2328
+ self._kill_proc(warm_proc)
2329
+ self._kill_proc(cold_proc)
2294
2330
 
2295
2331
  elapsed = (time.time() - t0) * 1000
2296
2332
  self._total_grades += 1
2297
- log(f"grade #{self._total_grades} {'warm' if warm else 'cold'} elapsed={elapsed:.0f}ms resp={len(resp)}ch")
2333
+ winner = winner_label or "none"
2334
+ log(f"grade #{self._total_grades} race={'warm+cold' if warm_proc else 'cold'} won={winner} elapsed={elapsed:.0f}ms resp={len(resp)}ch")
2298
2335
 
2299
2336
  self._start_prewarm()
2300
2337
  return resp
@@ -3746,7 +3783,7 @@ function writeConfigEnv(opts) {
3746
3783
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
3747
3784
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
3748
3785
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
3749
- `SYNKRO_VERSION=${shellQuoteSingle("1.3.41")}`
3786
+ `SYNKRO_VERSION=${shellQuoteSingle("1.3.42")}`
3750
3787
  ];
3751
3788
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
3752
3789
  if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);