forgexa-cli 1.13.5__tar.gz → 1.13.6__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.13.5 → forgexa_cli-1.13.6}/PKG-INFO +1 -1
- {forgexa_cli-1.13.5 → forgexa_cli-1.13.6}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.13.5 → forgexa_cli-1.13.6}/forgexa_cli/daemon.py +63 -5
- {forgexa_cli-1.13.5 → forgexa_cli-1.13.6}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.13.5 → forgexa_cli-1.13.6}/pyproject.toml +1 -1
- {forgexa_cli-1.13.5 → forgexa_cli-1.13.6}/README.md +0 -0
- {forgexa_cli-1.13.5 → forgexa_cli-1.13.6}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.13.5 → forgexa_cli-1.13.6}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.13.5 → forgexa_cli-1.13.6}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.13.5 → forgexa_cli-1.13.6}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.13.5 → forgexa_cli-1.13.6}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.13.5 → forgexa_cli-1.13.6}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.13.5 → forgexa_cli-1.13.6}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.13.5 → forgexa_cli-1.13.6}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.13.5 → forgexa_cli-1.13.6}/setup.cfg +0 -0
- {forgexa_cli-1.13.5 → forgexa_cli-1.13.6}/tests/test_auth_and_runtime_commands.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.13.
|
|
2
|
+
__version__ = "1.13.6"
|
|
@@ -523,7 +523,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
523
523
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
524
524
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
525
525
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
526
|
-
DAEMON_VERSION = "1.13.
|
|
526
|
+
DAEMON_VERSION = "1.13.6"
|
|
527
527
|
|
|
528
528
|
|
|
529
529
|
def _detect_client_type() -> str:
|
|
@@ -1585,6 +1585,14 @@ class WorkspaceManager:
|
|
|
1585
1585
|
"connection closed",
|
|
1586
1586
|
"connection refused",
|
|
1587
1587
|
"network is unreachable",
|
|
1588
|
+
# DNS resolution failures — transient (network reconnect, DHCP renewal,
|
|
1589
|
+
# macOS resolver flap). macOS/BSD SSH prints "nodename nor servname
|
|
1590
|
+
# provided"; Linux SSH/git prints "name or service not known" or
|
|
1591
|
+
# "temporary failure in name resolution".
|
|
1592
|
+
"nodename nor servname",
|
|
1593
|
+
"name or service not known",
|
|
1594
|
+
"could not resolve host",
|
|
1595
|
+
"temporary failure in name resolution",
|
|
1588
1596
|
)
|
|
1589
1597
|
return any(marker in err for marker in retry_markers)
|
|
1590
1598
|
|
|
@@ -2899,9 +2907,12 @@ class ProcessManager:
|
|
|
2899
2907
|
"connection reset",
|
|
2900
2908
|
"connection timed out",
|
|
2901
2909
|
"connection error",
|
|
2910
|
+
"failed to get response from the ai model",
|
|
2902
2911
|
"name or service not known",
|
|
2903
2912
|
"no such host",
|
|
2904
2913
|
"network is unreachable",
|
|
2914
|
+
"websockettransporterror",
|
|
2915
|
+
"websocket receive failed",
|
|
2905
2916
|
# "api error" removed: too broad — matches agent-generated code/output
|
|
2906
2917
|
# discussing API errors. Real API transport errors are covered by the
|
|
2907
2918
|
# connection patterns above (refused, reset, timed out, etc.).
|
|
@@ -3032,6 +3043,21 @@ class ProcessManager:
|
|
|
3032
3043
|
else:
|
|
3033
3044
|
has_result = True
|
|
3034
3045
|
has_meaningful_content = True
|
|
3046
|
+
elif ev_type == "session.error":
|
|
3047
|
+
err = data.get("data") or {}
|
|
3048
|
+
if isinstance(err, dict):
|
|
3049
|
+
msg = (
|
|
3050
|
+
err.get("message")
|
|
3051
|
+
or err.get("error")
|
|
3052
|
+
or err.get("errorType")
|
|
3053
|
+
or ""
|
|
3054
|
+
)
|
|
3055
|
+
if msg:
|
|
3056
|
+
error_messages.append(str(msg))
|
|
3057
|
+
elif isinstance(err, str) and err.strip():
|
|
3058
|
+
error_messages.append(err)
|
|
3059
|
+
elif isinstance(data.get("message"), str) and data.get("message", "").strip():
|
|
3060
|
+
error_messages.append(data["message"])
|
|
3035
3061
|
elif ev_type == "error":
|
|
3036
3062
|
msg = data.get("message", "")
|
|
3037
3063
|
if msg:
|
|
@@ -4198,7 +4224,27 @@ class ProcessManager:
|
|
|
4198
4224
|
# Copilot always exits 0 on normal completion; check result.exitCode
|
|
4199
4225
|
# from the JSONL "result" event for a true success signal.
|
|
4200
4226
|
copilot_exit = self._extract_copilot_exit_code(stdout)
|
|
4201
|
-
|
|
4227
|
+
signals = self._extract_output_signals(stdout)
|
|
4228
|
+
completed_without_result = bool(
|
|
4229
|
+
signals["has_turn_completed"]
|
|
4230
|
+
and signals["has_assistant_events"]
|
|
4231
|
+
and signals["has_meaningful_content"]
|
|
4232
|
+
and not signals["has_result"]
|
|
4233
|
+
and not signals["has_turn_failed"]
|
|
4234
|
+
and not signals["error_messages"]
|
|
4235
|
+
)
|
|
4236
|
+
structured_errors = [
|
|
4237
|
+
str(msg).strip()
|
|
4238
|
+
for msg in (signals.get("error_messages") or [])
|
|
4239
|
+
if str(msg).strip()
|
|
4240
|
+
]
|
|
4241
|
+
copilot_specific_error = ""
|
|
4242
|
+
for msg in reversed(structured_errors):
|
|
4243
|
+
prefix = "Copilot exited with code "
|
|
4244
|
+
suffix = msg[len(prefix):].strip() if msg.startswith(prefix) else ""
|
|
4245
|
+
if not (msg.startswith(prefix) and suffix.isdigit()):
|
|
4246
|
+
copilot_specific_error = msg
|
|
4247
|
+
break
|
|
4202
4248
|
effective_rc = copilot_exit if copilot_exit is not None else returncode
|
|
4203
4249
|
|
|
4204
4250
|
if effective_rc == 0 and returncode == 0:
|
|
@@ -4236,7 +4282,13 @@ class ProcessManager:
|
|
|
4236
4282
|
# Retry 2: if still failing AND we haven't already tried both drops,
|
|
4237
4283
|
# the error is likely auth/quota — log actionable hint.
|
|
4238
4284
|
_no_tools = not self._copilot_called_any_tools(stdout)
|
|
4239
|
-
if
|
|
4285
|
+
if (
|
|
4286
|
+
effective_rc == 1
|
|
4287
|
+
and _no_tools
|
|
4288
|
+
and reasoning
|
|
4289
|
+
and not _retry_without_effort
|
|
4290
|
+
and not copilot_specific_error
|
|
4291
|
+
):
|
|
4240
4292
|
logger.warning(
|
|
4241
4293
|
"Copilot exitCode=1 with no tool calls for task %s — "
|
|
4242
4294
|
"retrying without --effort (flag may be unsupported by this CLI version). "
|
|
@@ -4248,7 +4300,7 @@ class ProcessManager:
|
|
|
4248
4300
|
_retry_without_effort=True,
|
|
4249
4301
|
)
|
|
4250
4302
|
# Exhausted retries — emit an actionable diagnostic hint
|
|
4251
|
-
if effective_rc == 1 and _no_tools:
|
|
4303
|
+
if effective_rc == 1 and _no_tools and not copilot_specific_error:
|
|
4252
4304
|
logger.error(
|
|
4253
4305
|
"Copilot exitCode=1 with no tool calls for task %s after retries. "
|
|
4254
4306
|
"Likely causes: (1) GitHub auth expired — run `gh auth status` and "
|
|
@@ -4257,12 +4309,18 @@ class ProcessManager:
|
|
|
4257
4309
|
"To reproduce manually: %s",
|
|
4258
4310
|
task_id, agent.version, _cmd_display,
|
|
4259
4311
|
)
|
|
4312
|
+
if copilot_specific_error:
|
|
4313
|
+
failure_error = copilot_specific_error[:1500]
|
|
4314
|
+
elif stderr.strip():
|
|
4315
|
+
failure_error = f"Copilot exited with code {effective_rc}: {stderr[-500:].strip()}"
|
|
4316
|
+
else:
|
|
4317
|
+
failure_error = f"Copilot exited with code {effective_rc}"
|
|
4260
4318
|
return TaskResult(
|
|
4261
4319
|
status="failed",
|
|
4262
4320
|
exit_code=effective_rc,
|
|
4263
4321
|
stdout=stdout[-settings.AGENT_MAX_OUTPUT_SIZE:],
|
|
4264
4322
|
stderr=stderr[-10000:],
|
|
4265
|
-
error=
|
|
4323
|
+
error=failure_error,
|
|
4266
4324
|
metrics=metrics,
|
|
4267
4325
|
)
|
|
4268
4326
|
except asyncio.TimeoutError as exc:
|
|
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
|