forgexa-cli 1.38.2__tar.gz → 1.38.4__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.38.2 → forgexa_cli-1.38.4}/PKG-INFO +1 -1
  2. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/__init__.py +1 -1
  3. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/daemon.py +85 -3
  4. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli.egg-info/PKG-INFO +1 -1
  5. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/pyproject.toml +1 -1
  6. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/README.md +0 -0
  7. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/_build_config.py +0 -0
  8. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/_local_bind.py +0 -0
  9. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/agent_core.py +0 -0
  10. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/autoupgrade.py +0 -0
  11. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/main.py +0 -0
  12. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/py.typed +0 -0
  13. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli.egg-info/SOURCES.txt +0 -0
  14. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli.egg-info/dependency_links.txt +0 -0
  15. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli.egg-info/entry_points.txt +0 -0
  16. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli.egg-info/requires.txt +0 -0
  17. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli.egg-info/top_level.txt +0 -0
  18. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/setup.cfg +0 -0
  19. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_auth_and_runtime_commands.py +0 -0
  20. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_autoupgrade.py +0 -0
  21. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_check_command.py +0 -0
  22. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_expiry_warnings_and_revoke.py +0 -0
  23. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_local_bind_commands.py +0 -0
  24. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_runtime_credentials.py +0 -0
  25. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_session_credentials.py +0 -0
  26. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_silent_install.py +0 -0
  27. {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/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.38.2
3
+ Version: 1.38.4
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.38.2"
2
+ __version__ = "1.38.4"
@@ -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.38.2"
913
+ DAEMON_VERSION = "1.38.4"
914
914
 
915
915
 
916
916
  def _detect_client_type() -> str:
@@ -10881,6 +10881,11 @@ class RuntimeDaemon:
10881
10881
  "copilot": "Install VS Code GitHub Copilot Chat extension, or run: gh copilot. Then: gh auth login",
10882
10882
  }
10883
10883
  hint = _INSTALL_HINTS.get(agent_type, f"install the '{agent_type}' CLI tool")
10884
+ no_agent_attempts = (
10885
+ [{"agent": agent_type, "outcome": "no_agent"}]
10886
+ if agent_type in AgentDiscovery.AGENT_REGISTRY
10887
+ else []
10888
+ )
10884
10889
  await conn.client.post(
10885
10890
  f"{reporter_url}/complete",
10886
10891
  json={
@@ -10890,6 +10895,10 @@ class RuntimeDaemon:
10890
10895
  f"Install it: {hint}"
10891
10896
  ),
10892
10897
  "failure_code": "no_agent",
10898
+ "output_result": {
10899
+ "attempt_chain": no_agent_attempts,
10900
+ "fallback_scope": "override" if agent_override else "auto",
10901
+ },
10893
10902
  },
10894
10903
  timeout=10,
10895
10904
  )
@@ -11016,10 +11025,57 @@ class RuntimeDaemon:
11016
11025
  # Flush any remaining buffered lines after agent finishes
11017
11026
  await _flush_output_to_server()
11018
11027
 
11028
+ def _generation_duration_ms(task_result: TaskResult) -> int:
11029
+ seconds = (task_result.metrics or {}).get("duration_seconds")
11030
+ if (
11031
+ isinstance(seconds, (int, float))
11032
+ and not isinstance(seconds, bool)
11033
+ and seconds >= 0
11034
+ ):
11035
+ return int(seconds * 1000)
11036
+ return 0
11037
+
11038
+ def _generation_attempt(
11039
+ agent_id: str,
11040
+ task_result: TaskResult,
11041
+ *,
11042
+ rate_limited: bool,
11043
+ ) -> dict:
11044
+ if task_result.status == "success":
11045
+ outcome = "success"
11046
+ elif rate_limited:
11047
+ outcome = "rate_limited"
11048
+ elif task_result.failure_code == "agent_idle_timeout":
11049
+ outcome = "timeout"
11050
+ elif isinstance(task_result.exit_code, int) and task_result.exit_code != 0:
11051
+ outcome = "exit_code"
11052
+ else:
11053
+ outcome = "error"
11054
+ entry = {
11055
+ "agent": agent_id,
11056
+ "outcome": outcome,
11057
+ "duration_ms": _generation_duration_ms(task_result),
11058
+ }
11059
+ if outcome == "exit_code":
11060
+ entry["detail"] = f"rc={task_result.exit_code}"
11061
+ return entry
11062
+
11019
11063
  # 3.5 Agent fallback: if the chosen agent hit a rate/quota limit,
11020
- # try the next available agent before giving up.
11064
+ # try the next available agent before giving up. An explicit
11065
+ # override is deliberately a single-agent attempt.
11066
+ fallback_scope = "override" if agent_override else "auto"
11067
+ allow_fallback = not bool(agent_override)
11068
+ generation_attempts: list[dict] = []
11069
+ is_rate_limited = self.process_manager.is_rate_limited(result)
11070
+ generation_attempts.append(_generation_attempt(
11071
+ agent.agent_id,
11072
+ result,
11073
+ rate_limited=is_rate_limited,
11074
+ ))
11021
11075
  _aj_tried: set[str] = {agent.agent_id}
11022
- while self.process_manager.is_rate_limited(result):
11076
+ if is_rate_limited and not allow_fallback and not result.failure_code:
11077
+ result.failure_code = "agent_rate_limited"
11078
+ while is_rate_limited and allow_fallback:
11023
11079
  _aj_fallback = self._select_fallback_agent(agent.agent_id, [], _aj_tried)
11024
11080
  if not _aj_fallback:
11025
11081
  # All agents exhausted — signal server not to re-enqueue.
@@ -11073,6 +11129,17 @@ class RuntimeDaemon:
11073
11129
  except asyncio.CancelledError:
11074
11130
  pass
11075
11131
  await _flush_output_to_server()
11132
+ is_rate_limited = self.process_manager.is_rate_limited(result)
11133
+ generation_attempts.append(_generation_attempt(
11134
+ agent.agent_id,
11135
+ result,
11136
+ rate_limited=is_rate_limited,
11137
+ ))
11138
+
11139
+ generation_attempt_count = len(generation_attempts)
11140
+ generation_duration_ms = sum(
11141
+ entry["duration_ms"] for entry in generation_attempts
11142
+ )
11076
11143
 
11077
11144
  # 4. Capture branch/commit metadata without mutating the repository.
11078
11145
  # Workspace-mode AIJobs are prompt-output tasks: scenario generation,
@@ -11118,6 +11185,8 @@ class RuntimeDaemon:
11118
11185
  "files_changed": result.files_changed,
11119
11186
  "lines_added": result.lines_added,
11120
11187
  "lines_removed": result.lines_removed,
11188
+ "attempt_chain": generation_attempts,
11189
+ "fallback_scope": fallback_scope,
11121
11190
  },
11122
11191
  "tier_used": "agent_cli",
11123
11192
  "resolved_agent": agent.agent_id,
@@ -11125,6 +11194,19 @@ class RuntimeDaemon:
11125
11194
  "error": result.error if result.status != "success" else "",
11126
11195
  "failure_code": _failure_code,
11127
11196
  }
11197
+ if task_type == "test_script_generate":
11198
+ complete_payload["output_result"]["generation_metadata"] = {
11199
+ "response_chars": len(normalized_output),
11200
+ "agent": agent.agent_id,
11201
+ "attempt_count": generation_attempt_count,
11202
+ "duration_ms": generation_duration_ms,
11203
+ "truncation_suspected": len(result.stdout or "") >= settings.AGENT_MAX_OUTPUT_SIZE,
11204
+ }
11205
+ if scripts:
11206
+ complete_payload["output_result"]["script_generation_metadata"] = {
11207
+ scenario_id: {"response_chars": len(script)}
11208
+ for scenario_id, script in scripts.items()
11209
+ }
11128
11210
  if scenario_items:
11129
11211
  complete_payload["output_result"]["items"] = scenario_items
11130
11212
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.38.2
3
+ Version: 1.38.4
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.38.2"
3
+ version = "1.38.4"
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