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.
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/PKG-INFO +1 -1
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/daemon.py +85 -3
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/pyproject.toml +1 -1
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/README.md +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/_local_bind.py +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/agent_core.py +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/setup.cfg +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_expiry_warnings_and_revoke.py +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_local_bind_commands.py +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_runtime_credentials.py +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_session_credentials.py +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.38.2 → forgexa_cli-1.38.4}/tests/test_upgrade_observability.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.38.
|
|
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.
|
|
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
|
-
|
|
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
|
|
|
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
|
|
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
|