forgexa-cli 1.2.3__tar.gz → 1.2.7__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.2.3
3
+ Version: 1.2.7
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: MIT
@@ -1,2 +1,2 @@
1
1
  """forgexa-cli — Forgexa command-line client."""
2
- __version__ = "1.2.3"
2
+ __version__ = "1.2.7"
@@ -242,6 +242,47 @@ def get_hardware_id() -> str:
242
242
  return uuid.uuid4().hex[:24]
243
243
 
244
244
 
245
+ def get_os_info() -> str:
246
+ """Return a concise OS/arch summary string, e.g. 'macOS 15.0 arm64', 'Ubuntu 24.04 x86_64'."""
247
+ system = platform.system()
248
+ machine = platform.machine()
249
+ if system == "Darwin":
250
+ # e.g. "macOS 15.0 arm64"
251
+ mac_ver = platform.mac_ver()[0] or platform.release()
252
+ return f"macOS {mac_ver} {machine}"
253
+ elif system == "Linux":
254
+ # Try to get distro name+version from /etc/os-release
255
+ distro = _get_linux_distro()
256
+ if distro:
257
+ return f"{distro} {machine}"
258
+ return f"Linux {platform.release().split('-')[0]} {machine}"
259
+ elif system == "Windows":
260
+ # e.g. "Windows 10.0 AMD64"
261
+ win_ver = platform.version().split('.')[0:2]
262
+ return f"Windows {'.'.join(win_ver)} {machine}"
263
+ else:
264
+ return f"{system} {platform.release()} {machine}"
265
+
266
+
267
+ def _get_linux_distro() -> str:
268
+ """Parse /etc/os-release to get distro name and version, e.g. 'Ubuntu 24.04'."""
269
+ try:
270
+ with open("/etc/os-release") as f:
271
+ info = {}
272
+ for line in f:
273
+ line = line.strip()
274
+ if "=" in line:
275
+ key, _, val = line.partition("=")
276
+ info[key] = val.strip('"')
277
+ name = info.get("NAME", "")
278
+ version = info.get("VERSION_ID", "")
279
+ if name:
280
+ return f"{name} {version}".strip()
281
+ except OSError:
282
+ pass
283
+ return ""
284
+
285
+
245
286
  # ── Data Classes ──
246
287
 
247
288
 
@@ -726,6 +767,35 @@ class WorkspaceManager:
726
767
  )
727
768
  except RuntimeError as exc2:
728
769
  logger.warning("Failed to reset to origin/%s: %s", default_branch, exc2)
770
+ else:
771
+ # Non-fresh-start (design/coding/testing): ensure working tree
772
+ # is on the correct branch and has the latest commits from remote.
773
+ # This is critical for cross-machine execution where a previous
774
+ # node on another daemon pushed commits to the branch.
775
+ logger.info("Syncing worktree %s to latest origin/%s", ws_path, branch_name)
776
+ try:
777
+ await self._git("checkout", branch_name, cwd=ws_path)
778
+ except RuntimeError:
779
+ # Branch might not exist locally yet — create tracking branch
780
+ try:
781
+ await self._git(
782
+ "checkout", "-B", branch_name, f"origin/{branch_name}",
783
+ cwd=ws_path,
784
+ )
785
+ except RuntimeError as exc:
786
+ logger.warning("Failed to checkout %s: %s", branch_name, exc)
787
+ # Reset working tree to match remote branch (fast-forward)
788
+ try:
789
+ await self._git(
790
+ "reset", "--hard", f"origin/{branch_name}",
791
+ cwd=ws_path,
792
+ )
793
+ except RuntimeError as exc:
794
+ # Remote branch might not exist yet (first node in graph)
795
+ logger.debug(
796
+ "Could not reset to origin/%s: %s — branch may not exist on remote yet",
797
+ branch_name, exc,
798
+ )
729
799
  return ws_path
730
800
 
731
801
  # Ensure _main repo is present and up-to-date
@@ -1043,7 +1113,22 @@ class ProcessManager:
1043
1113
  if not proc.stdout:
1044
1114
  return
1045
1115
  while True:
1046
- line_bytes = await proc.stdout.readline()
1116
+ try:
1117
+ line_bytes = await proc.stdout.readline()
1118
+ except ValueError:
1119
+ # Line exceeded stream buffer limit – fall back to reading
1120
+ # remaining data in bulk to avoid losing output.
1121
+ remaining = await proc.stdout.read()
1122
+ if remaining:
1123
+ for chunk_line in remaining.decode(errors="replace").split("\n"):
1124
+ if chunk_line:
1125
+ stdout_lines.append(chunk_line)
1126
+ if on_chunk:
1127
+ try:
1128
+ await on_chunk([chunk_line])
1129
+ except Exception:
1130
+ pass
1131
+ break
1047
1132
  if not line_bytes:
1048
1133
  break
1049
1134
  line = line_bytes.decode(errors="replace").rstrip("\n")
@@ -1107,6 +1192,7 @@ class ProcessManager:
1107
1192
  stdin=asyncio.subprocess.PIPE,
1108
1193
  cwd=str(cwd),
1109
1194
  env=env,
1195
+ limit=10 * 1024 * 1024, # 10MB line buffer for large JSON output
1110
1196
  )
1111
1197
  self.active_processes[task_id] = proc
1112
1198
  stdout, stderr, returncode = await self._stream_process(
@@ -1745,6 +1831,7 @@ class HeartbeatService:
1745
1831
  "active_tasks": self._active_tasks,
1746
1832
  "available_agents": self._agents,
1747
1833
  "system_metrics": self._collect_system_metrics(),
1834
+ "os_info": get_os_info(),
1748
1835
  },
1749
1836
  timeout=10,
1750
1837
  )
@@ -1925,6 +2012,7 @@ class ServerConnection:
1925
2012
  "daemon_id": self.daemon_id,
1926
2013
  "hardware_id": self.hardware_id,
1927
2014
  "device_name": platform.node(),
2015
+ "os_info": get_os_info(),
1928
2016
  "available_agents": agent_dicts,
1929
2017
  "max_concurrent_tasks": max_concurrent,
1930
2018
  "capabilities": {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.2.3
3
+ Version: 1.2.7
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: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "forgexa-cli"
3
- version = "1.2.3"
3
+ version = "1.2.7"
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 = { text = "MIT" }
File without changes
File without changes