forgexa-cli 1.37.4__tar.gz → 1.38.0__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.
Files changed (27) hide show
  1. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/PKG-INFO +1 -1
  2. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/forgexa_cli/__init__.py +1 -1
  3. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/forgexa_cli/daemon.py +70 -12
  4. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/forgexa_cli.egg-info/PKG-INFO +1 -1
  5. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/pyproject.toml +1 -1
  6. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/README.md +0 -0
  7. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/forgexa_cli/_build_config.py +0 -0
  8. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/forgexa_cli/_local_bind.py +0 -0
  9. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/forgexa_cli/agent_core.py +0 -0
  10. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/forgexa_cli/autoupgrade.py +0 -0
  11. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/forgexa_cli/main.py +0 -0
  12. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/forgexa_cli/py.typed +0 -0
  13. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/forgexa_cli.egg-info/SOURCES.txt +0 -0
  14. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/forgexa_cli.egg-info/dependency_links.txt +0 -0
  15. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/forgexa_cli.egg-info/entry_points.txt +0 -0
  16. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/forgexa_cli.egg-info/requires.txt +0 -0
  17. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/forgexa_cli.egg-info/top_level.txt +0 -0
  18. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/setup.cfg +0 -0
  19. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/tests/test_auth_and_runtime_commands.py +0 -0
  20. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/tests/test_autoupgrade.py +0 -0
  21. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/tests/test_check_command.py +0 -0
  22. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/tests/test_expiry_warnings_and_revoke.py +0 -0
  23. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/tests/test_local_bind_commands.py +0 -0
  24. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/tests/test_runtime_credentials.py +0 -0
  25. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/tests/test_session_credentials.py +0 -0
  26. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/tests/test_silent_install.py +0 -0
  27. {forgexa_cli-1.37.4 → forgexa_cli-1.38.0}/tests/test_upgrade_observability.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.37.4
3
+ Version: 1.38.0
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.37.4"
2
+ __version__ = "1.38.0"
@@ -910,7 +910,7 @@ except (ImportError, ModuleNotFoundError):
910
910
  # DAEMON_VERSION is the protocol/logic version of the daemon code.
911
911
  # Kept in sync with pyproject.toml version via bump-version.sh.
912
912
  # CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
913
- DAEMON_VERSION = "1.37.4"
913
+ DAEMON_VERSION = "1.38.0"
914
914
 
915
915
 
916
916
  def _detect_client_type() -> str:
@@ -1245,6 +1245,8 @@ class TaskResult:
1245
1245
  observations: list[dict] = field(default_factory=list)
1246
1246
  metrics: dict = field(default_factory=dict)
1247
1247
  git: dict = field(default_factory=dict)
1248
+ # Set from the complete agent stream before stdout is retained as a bounded tail.
1249
+ has_verified_completion: bool = False
1248
1250
 
1249
1251
 
1250
1252
  def _filter_local_bind_task_result(result: "TaskResult") -> None:
@@ -2833,24 +2835,68 @@ class WorkspaceManager:
2833
2835
  )
2834
2836
 
2835
2837
  try:
2836
- await self._git(
2837
- "fetch", "origin", f"{target_branch}:refs/remotes/origin/{target_branch}",
2838
+ remote_branch = await self._git(
2839
+ "ls-remote", "--heads", "origin", f"refs/heads/{target_branch}",
2838
2840
  cwd=local_path,
2839
2841
  )
2840
2842
  except RuntimeError as exc:
2841
2843
  raise RuntimeError(
2842
- f"local_binding_unavailable: failed to fetch '{target_branch}' from origin: {exc}"
2844
+ f"local_binding_unavailable: failed to inspect '{target_branch}' on origin: {exc}"
2843
2845
  ) from exc
2844
2846
 
2845
- ahead_output = await self._git(
2846
- "rev-list", f"origin/{target_branch}..HEAD", "--count", cwd=local_path,
2847
- )
2848
- if ahead_output.strip() not in ("", "0"):
2849
- raise RuntimeError(
2850
- f"local_binding_unavailable: local branch '{target_branch}' has "
2851
- f"{ahead_output.strip()} unpushed commit(s) ahead of origin — push them "
2852
- "yourself first, Forgexa will not force-sync your real directory."
2847
+ if remote_branch.strip():
2848
+ try:
2849
+ await self._git(
2850
+ "fetch", "origin", f"{target_branch}:refs/remotes/origin/{target_branch}",
2851
+ cwd=local_path,
2852
+ )
2853
+ except RuntimeError as exc:
2854
+ raise RuntimeError(
2855
+ f"local_binding_unavailable: failed to fetch '{target_branch}' from origin: {exc}"
2856
+ ) from exc
2857
+
2858
+ ahead_output = await self._git(
2859
+ "rev-list", f"origin/{target_branch}..HEAD", "--count", cwd=local_path,
2860
+ )
2861
+ if ahead_output.strip() not in ("", "0"):
2862
+ raise RuntimeError(
2863
+ f"local_binding_unavailable: local branch '{target_branch}' has "
2864
+ f"{ahead_output.strip()} unpushed commit(s) ahead of origin — push them "
2865
+ "yourself first, Forgexa will not force-sync your real directory."
2866
+ )
2867
+ else:
2868
+ can_bootstrap_branch = (
2869
+ task.node_type == "analysis"
2870
+ and (task.input_data or {}).get("analysis_mode") == "fresh"
2871
+ and not task.required_commit_shas
2853
2872
  )
2873
+ if not can_bootstrap_branch:
2874
+ raise RuntimeError(
2875
+ f"local_binding_unavailable: remote branch '{target_branch}' does not exist; "
2876
+ "only a fresh initial analysis may bootstrap it"
2877
+ )
2878
+
2879
+ default_branch = str((project or {}).get("default_branch") or "main").strip() or "main"
2880
+ try:
2881
+ await self._git(
2882
+ "fetch", "origin", f"{default_branch}:refs/remotes/origin/{default_branch}",
2883
+ cwd=local_path,
2884
+ )
2885
+ local_head = (await self._git("rev-parse", "HEAD", cwd=local_path)).strip()
2886
+ default_head = (
2887
+ await self._git("rev-parse", f"origin/{default_branch}", cwd=local_path)
2888
+ ).strip()
2889
+ except RuntimeError as exc:
2890
+ raise RuntimeError(
2891
+ "local_binding_unavailable: failed to verify initial branch against "
2892
+ f"origin/{default_branch}: {exc}"
2893
+ ) from exc
2894
+ if local_head != default_head:
2895
+ raise RuntimeError(
2896
+ f"local_binding_unavailable: remote branch '{target_branch}' does not exist and "
2897
+ f"local branch is not at origin/{default_branch}; refusing to bootstrap from "
2898
+ "unpushed or stale history"
2899
+ )
2854
2900
 
2855
2901
  # Cross-phase handoff verification (docs/designs/forgexa-ide-
2856
2902
  # design.md §6.7): every direct predecessor's commit SHA must
@@ -4745,6 +4791,8 @@ class ProcessManager:
4745
4791
  @staticmethod
4746
4792
  def has_verified_agent_completion(result: "TaskResult") -> bool:
4747
4793
  """Return True when structured output proves the agent completed a real turn."""
4794
+ if result.has_verified_completion:
4795
+ return True
4748
4796
  combined = "\n".join(part for part in (result.stdout, result.stderr) if part)
4749
4797
  signals = ProcessManager._extract_output_signals(combined)
4750
4798
  return bool(
@@ -5927,6 +5975,13 @@ class ProcessManager:
5927
5975
  and not signals["has_turn_failed"]
5928
5976
  and not signals["error_messages"]
5929
5977
  )
5978
+ has_verified_completion = bool(
5979
+ signals["has_assistant_events"]
5980
+ and signals["has_meaningful_content"]
5981
+ and (signals["has_result"] or signals["has_turn_completed"])
5982
+ and not signals["has_turn_failed"]
5983
+ and not signals["error_messages"]
5984
+ )
5930
5985
  structured_errors = [
5931
5986
  str(msg).strip()
5932
5987
  for msg in (signals.get("error_messages") or [])
@@ -5948,6 +6003,7 @@ class ProcessManager:
5948
6003
  stdout=stdout[-settings.AGENT_MAX_OUTPUT_SIZE:],
5949
6004
  stderr=stderr[-10000:],
5950
6005
  metrics=metrics,
6006
+ has_verified_completion=has_verified_completion,
5951
6007
  )
5952
6008
  elif not copilot_specific_error and copilot_exit is None and completed_without_result:
5953
6009
  if returncode != 0:
@@ -5964,6 +6020,7 @@ class ProcessManager:
5964
6020
  stdout=stdout[-settings.AGENT_MAX_OUTPUT_SIZE:],
5965
6021
  stderr=stderr[-10000:],
5966
6022
  metrics=metrics,
6023
+ has_verified_completion=has_verified_completion,
5967
6024
  )
5968
6025
  else:
5969
6026
  # --- Immediate-failure retry ladder ---
@@ -6486,6 +6543,7 @@ class ProgressReporter:
6486
6543
  "exit_code": result.exit_code,
6487
6544
  "stdout_tail": result.stdout[-20000:] if result.stdout else "",
6488
6545
  "stderr_tail": result.stderr[-5000:] if result.stderr else "",
6546
+ "verified_completion": result.has_verified_completion,
6489
6547
  "error": result.error,
6490
6548
  "failure_code": result.failure_code,
6491
6549
  "preflight": result.preflight,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.37.4
3
+ Version: 1.38.0
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.37.4"
3
+ version = "1.38.0"
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