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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.20.2
3
+ Version: 1.20.3
4
4
  Summary: Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform
5
5
  Author-email: Jason Sun <dev.winds@gmail.com>
6
6
  License-Expression: MIT
@@ -1,2 +1,2 @@
1
1
  """forgexa-cli — Forgexa command-line client."""
2
- __version__ = "1.20.2"
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.2"
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
- result = await self.process_manager.run_agent(
8896
- agent, fake_task, workspace_path, on_chunk=on_chunk,
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
- result = await self.process_manager.run_agent(
8929
- agent, fake_task, workspace_path, on_chunk=on_chunk,
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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.20.2
3
+ Version: 1.20.3
4
4
  Summary: Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform
5
5
  Author-email: Jason Sun <dev.winds@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "forgexa-cli"
3
- version = "1.20.2"
3
+ version = "1.20.3"
4
4
  description = "Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform"
5
5
  requires-python = ">=3.9"
6
6
  license = "MIT"
File without changes
File without changes