forgexa-cli 1.20.2__tar.gz → 1.20.3__tar.gz
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.
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/PKG-INFO +1 -1
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/forgexa_cli/daemon.py +70 -7
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/pyproject.toml +1 -1
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/README.md +0 -0
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/setup.cfg +0 -0
- {forgexa_cli-1.20.2 → forgexa_cli-1.20.3}/tests/test_auth_and_runtime_commands.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.20.
|
|
2
|
+
__version__ = "1.20.3"
|
|
@@ -555,7 +555,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
555
555
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
556
556
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
557
557
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
558
|
-
DAEMON_VERSION = "1.20.
|
|
558
|
+
DAEMON_VERSION = "1.20.3"
|
|
559
559
|
|
|
560
560
|
|
|
561
561
|
def _detect_client_type() -> str:
|
|
@@ -8892,9 +8892,46 @@ class RuntimeDaemon:
|
|
|
8892
8892
|
if pending_count >= 10 or (now - _chunk_state["last_flush"]) >= 8.0:
|
|
8893
8893
|
await _flush_output_to_server()
|
|
8894
8894
|
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
)
|
|
8895
|
+
# P1-4 fix: periodic keep-alive heartbeat, independent of output activity.
|
|
8896
|
+
# report_ai_job_progress() on the server unconditionally renews
|
|
8897
|
+
# job.lease_until by AI_JOB_DISPATCHER_LEASE_SECONDS (600s) on every call,
|
|
8898
|
+
# but on_chunk above only POSTs when the agent actually produces new
|
|
8899
|
+
# stdout lines. A long silent stretch (agent "thinking", a quiet
|
|
8900
|
+
# build/test command, etc.) is explicitly tolerated for up to
|
|
8901
|
+
# AGENT_IDLE_TIMEOUT (1200s) before the daemon itself would consider the
|
|
8902
|
+
# agent hung — but the AIJob's server-side lease would already have
|
|
8903
|
+
# expired at 600s with no on_chunk activity, causing the dispatcher's
|
|
8904
|
+
# stale-job recovery to reset the job to pending and re-dispatch it while
|
|
8905
|
+
# this daemon execution is still legitimately running (duplicate
|
|
8906
|
+
# execution / clobbered state). A heartbeat well under the lease
|
|
8907
|
+
# duration closes that gap.
|
|
8908
|
+
_heartbeat_stop = asyncio.Event()
|
|
8909
|
+
|
|
8910
|
+
async def _lease_heartbeat():
|
|
8911
|
+
while not _heartbeat_stop.is_set():
|
|
8912
|
+
try:
|
|
8913
|
+
await asyncio.wait_for(_heartbeat_stop.wait(), timeout=60)
|
|
8914
|
+
except asyncio.TimeoutError:
|
|
8915
|
+
pass
|
|
8916
|
+
if _heartbeat_stop.is_set():
|
|
8917
|
+
break
|
|
8918
|
+
try:
|
|
8919
|
+
await conn.client.post(f"{reporter_url}/progress", json={}, timeout=10)
|
|
8920
|
+
except Exception:
|
|
8921
|
+
pass # never let heartbeat errors affect agent execution
|
|
8922
|
+
|
|
8923
|
+
_heartbeat_task = asyncio.create_task(_lease_heartbeat())
|
|
8924
|
+
try:
|
|
8925
|
+
result = await self.process_manager.run_agent(
|
|
8926
|
+
agent, fake_task, workspace_path, on_chunk=on_chunk,
|
|
8927
|
+
)
|
|
8928
|
+
finally:
|
|
8929
|
+
_heartbeat_stop.set()
|
|
8930
|
+
_heartbeat_task.cancel()
|
|
8931
|
+
try:
|
|
8932
|
+
await _heartbeat_task
|
|
8933
|
+
except asyncio.CancelledError:
|
|
8934
|
+
pass
|
|
8898
8935
|
# Flush any remaining buffered lines after agent finishes
|
|
8899
8936
|
await _flush_output_to_server()
|
|
8900
8937
|
|
|
@@ -8925,9 +8962,35 @@ class RuntimeDaemon:
|
|
|
8925
8962
|
agent = _aj_fallback
|
|
8926
8963
|
_aj_tried.add(agent.agent_id)
|
|
8927
8964
|
fake_task.agent_type = agent.agent_id
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
)
|
|
8965
|
+
# Same lease-renewal heartbeat as the primary run above (P1-4) —
|
|
8966
|
+
# a fallback agent run can also go silent for a while.
|
|
8967
|
+
_fb_heartbeat_stop = asyncio.Event()
|
|
8968
|
+
|
|
8969
|
+
async def _fb_lease_heartbeat():
|
|
8970
|
+
while not _fb_heartbeat_stop.is_set():
|
|
8971
|
+
try:
|
|
8972
|
+
await asyncio.wait_for(_fb_heartbeat_stop.wait(), timeout=60)
|
|
8973
|
+
except asyncio.TimeoutError:
|
|
8974
|
+
pass
|
|
8975
|
+
if _fb_heartbeat_stop.is_set():
|
|
8976
|
+
break
|
|
8977
|
+
try:
|
|
8978
|
+
await conn.client.post(f"{reporter_url}/progress", json={}, timeout=10)
|
|
8979
|
+
except Exception:
|
|
8980
|
+
pass
|
|
8981
|
+
|
|
8982
|
+
_fb_heartbeat_task = asyncio.create_task(_fb_lease_heartbeat())
|
|
8983
|
+
try:
|
|
8984
|
+
result = await self.process_manager.run_agent(
|
|
8985
|
+
agent, fake_task, workspace_path, on_chunk=on_chunk,
|
|
8986
|
+
)
|
|
8987
|
+
finally:
|
|
8988
|
+
_fb_heartbeat_stop.set()
|
|
8989
|
+
_fb_heartbeat_task.cancel()
|
|
8990
|
+
try:
|
|
8991
|
+
await _fb_heartbeat_task
|
|
8992
|
+
except asyncio.CancelledError:
|
|
8993
|
+
pass
|
|
8931
8994
|
await _flush_output_to_server()
|
|
8932
8995
|
|
|
8933
8996
|
# 4. Capture branch/commit metadata without mutating the repository.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|