forgexa-cli 1.27.3__tar.gz → 1.27.5__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.27.3 → forgexa_cli-1.27.5}/PKG-INFO +1 -1
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/forgexa_cli/agent_core.py +6 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/forgexa_cli/daemon.py +84 -10
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/pyproject.toml +1 -1
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/README.md +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/forgexa_cli/_local_bind.py +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/setup.cfg +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/tests/test_expiry_warnings_and_revoke.py +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/tests/test_local_bind_commands.py +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/tests/test_runtime_credentials.py +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/tests/test_session_credentials.py +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.27.3 → forgexa_cli-1.27.5}/tests/test_upgrade_observability.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.27.
|
|
2
|
+
__version__ = "1.27.5"
|
|
@@ -802,6 +802,12 @@ def _extract_output_signals(text: str) -> dict[str, Any]:
|
|
|
802
802
|
error_messages.append(err)
|
|
803
803
|
elif isinstance(data.get("message"), str) and data.get("message", "").strip():
|
|
804
804
|
error_messages.append(data["message"])
|
|
805
|
+
elif ev_type == "session.mcp_server_status_changed":
|
|
806
|
+
status_data = data.get("data") or {}
|
|
807
|
+
if isinstance(status_data, dict) and status_data.get("status") == "failed":
|
|
808
|
+
message = status_data.get("error") or status_data.get("message") or ""
|
|
809
|
+
if message:
|
|
810
|
+
error_messages.append(str(message))
|
|
805
811
|
elif ev_type == "error":
|
|
806
812
|
msg = data.get("message", "")
|
|
807
813
|
if msg:
|
|
@@ -909,7 +909,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
909
909
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
910
910
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
911
911
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
912
|
-
DAEMON_VERSION = "1.27.
|
|
912
|
+
DAEMON_VERSION = "1.27.5"
|
|
913
913
|
|
|
914
914
|
|
|
915
915
|
def _detect_client_type() -> str:
|
|
@@ -2268,6 +2268,21 @@ class WorkspaceManager:
|
|
|
2268
2268
|
)
|
|
2269
2269
|
return any(marker in err for marker in retry_markers)
|
|
2270
2270
|
|
|
2271
|
+
@staticmethod
|
|
2272
|
+
def _is_unreadable_git_object_error(exc: Exception | str) -> bool:
|
|
2273
|
+
detail = str(exc).lower()
|
|
2274
|
+
return any(marker in detail for marker in (
|
|
2275
|
+
"unable to read tree",
|
|
2276
|
+
"could not read tree",
|
|
2277
|
+
"cannot read tree",
|
|
2278
|
+
"bad tree object",
|
|
2279
|
+
"missing tree object",
|
|
2280
|
+
"bad object",
|
|
2281
|
+
"unknown object",
|
|
2282
|
+
"not a valid object",
|
|
2283
|
+
"无法读取树",
|
|
2284
|
+
))
|
|
2285
|
+
|
|
2271
2286
|
@staticmethod
|
|
2272
2287
|
def _safe_rmtree(path: Path) -> None:
|
|
2273
2288
|
"""Remove a directory tree, handling two distinct Windows failure modes.
|
|
@@ -3434,6 +3449,7 @@ class WorkspaceManager:
|
|
|
3434
3449
|
workspace_key: str, branch_name: str, *, fresh_start: bool = False,
|
|
3435
3450
|
project_key: str = "default", expect_branch: bool = False,
|
|
3436
3451
|
detach_head: bool = False,
|
|
3452
|
+
_recovered_unreadable_object: bool = False,
|
|
3437
3453
|
) -> Path:
|
|
3438
3454
|
main_repo = project_dir / "_main"
|
|
3439
3455
|
ws_path = project_dir / workspace_key
|
|
@@ -4212,6 +4228,36 @@ class WorkspaceManager:
|
|
|
4212
4228
|
return ws_path
|
|
4213
4229
|
except Exception as recovery_exc:
|
|
4214
4230
|
exc = recovery_exc
|
|
4231
|
+
if (
|
|
4232
|
+
not _recovered_unreadable_object
|
|
4233
|
+
and self._is_unreadable_git_object_error(exc)
|
|
4234
|
+
):
|
|
4235
|
+
logger.warning(
|
|
4236
|
+
"Fresh start could not read a Git object from %s; rebuilding _main cache",
|
|
4237
|
+
main_repo,
|
|
4238
|
+
)
|
|
4239
|
+
self._safe_rmtree(ws_path)
|
|
4240
|
+
await self._safe_rmtree_main(main_repo)
|
|
4241
|
+
if main_repo.exists():
|
|
4242
|
+
raise RuntimeError(
|
|
4243
|
+
f"Cannot rebuild local Git cache at {main_repo}; "
|
|
4244
|
+
"please stop the daemon and remove it manually."
|
|
4245
|
+
) from exc
|
|
4246
|
+
try:
|
|
4247
|
+
return await self._create_worktree_impl(
|
|
4248
|
+
project_dir, repo_url, default_branch,
|
|
4249
|
+
workspace_key, branch_name,
|
|
4250
|
+
fresh_start=fresh_start,
|
|
4251
|
+
project_key=project_key,
|
|
4252
|
+
expect_branch=expect_branch,
|
|
4253
|
+
detach_head=detach_head,
|
|
4254
|
+
_recovered_unreadable_object=True,
|
|
4255
|
+
)
|
|
4256
|
+
except Exception as recovery_exc:
|
|
4257
|
+
raise RuntimeError(
|
|
4258
|
+
"Failed to create fresh worktree after rebuilding a local Git cache "
|
|
4259
|
+
"with unreadable objects."
|
|
4260
|
+
) from recovery_exc
|
|
4215
4261
|
self._safe_rmtree(ws_path)
|
|
4216
4262
|
raise RuntimeError(
|
|
4217
4263
|
f"Failed to create clean fresh worktree for branch '{branch_name}'. "
|
|
@@ -4581,6 +4627,8 @@ class ProcessManager:
|
|
|
4581
4627
|
"network is unreachable",
|
|
4582
4628
|
"websockettransporterror",
|
|
4583
4629
|
"websocket receive failed",
|
|
4630
|
+
"421 misdirected request",
|
|
4631
|
+
"failed to initialize mcp client",
|
|
4584
4632
|
# "api error" removed: too broad — matches agent-generated code/output
|
|
4585
4633
|
# discussing API errors. Real API transport errors are covered by the
|
|
4586
4634
|
# connection patterns above (refused, reset, timed out, etc.).
|
|
@@ -5479,6 +5527,26 @@ class ProcessManager:
|
|
|
5479
5527
|
shutil.copy2(auth_src, isolated_data_dir / "auth.json")
|
|
5480
5528
|
env = agent_core.opencode_base_env(os.environ.copy(), data_home=tmp_data_root)
|
|
5481
5529
|
result = await self._run_cli(cmd, cwd, timeout, task_id, on_chunk=on_chunk, env=env)
|
|
5530
|
+
output = "\n".join(
|
|
5531
|
+
part for part in (result.stdout, result.stderr, result.error) if part
|
|
5532
|
+
).lower()
|
|
5533
|
+
if model_override and "model not found" in output and model_override.lower() in output:
|
|
5534
|
+
logger.warning(
|
|
5535
|
+
"OpenCode model override %r is unavailable for task %s; retrying without it",
|
|
5536
|
+
model_override,
|
|
5537
|
+
task_id,
|
|
5538
|
+
)
|
|
5539
|
+
retry_env = dict(env)
|
|
5540
|
+
retry_env.pop("FACTORY_OPENCODE_MODEL", None)
|
|
5541
|
+
cmd = agent_core.build_opencode_command(
|
|
5542
|
+
agent.command,
|
|
5543
|
+
cwd=cwd,
|
|
5544
|
+
prompt_file=prompt_file,
|
|
5545
|
+
)
|
|
5546
|
+
result = await self._run_cli(
|
|
5547
|
+
cmd, cwd, timeout, task_id, on_chunk=on_chunk, env=retry_env,
|
|
5548
|
+
)
|
|
5549
|
+
result.metrics["opencode_model_override_fallback"] = True
|
|
5482
5550
|
finally:
|
|
5483
5551
|
shutil.rmtree(tmp_data_root, ignore_errors=True)
|
|
5484
5552
|
|
|
@@ -5853,7 +5921,7 @@ class ProcessManager:
|
|
|
5853
5921
|
break
|
|
5854
5922
|
effective_rc = copilot_exit if copilot_exit is not None else returncode
|
|
5855
5923
|
|
|
5856
|
-
if
|
|
5924
|
+
if copilot_exit == 0 and returncode == 0:
|
|
5857
5925
|
return TaskResult(
|
|
5858
5926
|
status="success",
|
|
5859
5927
|
exit_code=0,
|
|
@@ -5861,14 +5929,15 @@ class ProcessManager:
|
|
|
5861
5929
|
stderr=stderr[-10000:],
|
|
5862
5930
|
metrics=metrics,
|
|
5863
5931
|
)
|
|
5864
|
-
elif
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5932
|
+
elif not copilot_specific_error and copilot_exit is None and completed_without_result:
|
|
5933
|
+
if returncode != 0:
|
|
5934
|
+
logger.warning(
|
|
5935
|
+
"Copilot exited with return code %s for task %s despite a completed assistant turn and no result event; recovering to validation path",
|
|
5936
|
+
returncode,
|
|
5937
|
+
task_id,
|
|
5938
|
+
)
|
|
5939
|
+
metrics["recovered_missing_result_event"] = True
|
|
5940
|
+
metrics["recovered_process_returncode"] = returncode
|
|
5872
5941
|
return TaskResult(
|
|
5873
5942
|
status="success",
|
|
5874
5943
|
exit_code=0,
|
|
@@ -5933,6 +6002,11 @@ class ProcessManager:
|
|
|
5933
6002
|
)
|
|
5934
6003
|
if copilot_specific_error:
|
|
5935
6004
|
failure_error = copilot_specific_error[:1500]
|
|
6005
|
+
elif copilot_exit is None and returncode == 0:
|
|
6006
|
+
stderr_detail = stderr[-500:].strip()
|
|
6007
|
+
failure_error = "Copilot exited without a successful result event"
|
|
6008
|
+
if stderr_detail:
|
|
6009
|
+
failure_error = f"{failure_error}: {stderr_detail}"
|
|
5936
6010
|
elif stderr.strip():
|
|
5937
6011
|
failure_error = f"Copilot exited with code {effective_rc}: {stderr[-500:].strip()}"
|
|
5938
6012
|
else:
|
|
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
|