forgexa-cli 1.76.0__tar.gz → 1.76.1__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 (31) hide show
  1. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/PKG-INFO +1 -1
  2. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli/__init__.py +1 -1
  3. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli/agent_core.py +37 -1
  4. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli/daemon.py +65 -13
  5. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli.egg-info/PKG-INFO +1 -1
  6. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/pyproject.toml +1 -1
  7. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/README.md +0 -0
  8. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli/_build_config.py +0 -0
  9. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli/_local_bind.py +0 -0
  10. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli/autoupgrade.py +0 -0
  11. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli/aws_evidence.py +0 -0
  12. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli/aws_logs_evidence.py +0 -0
  13. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli/main.py +0 -0
  14. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli/py.typed +0 -0
  15. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli/runtime_evidence.py +0 -0
  16. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli/zcode_protocol.py +0 -0
  17. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli.egg-info/SOURCES.txt +0 -0
  18. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli.egg-info/dependency_links.txt +0 -0
  19. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli.egg-info/entry_points.txt +0 -0
  20. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli.egg-info/requires.txt +0 -0
  21. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/forgexa_cli.egg-info/top_level.txt +0 -0
  22. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/setup.cfg +0 -0
  23. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/tests/test_auth_and_runtime_commands.py +0 -0
  24. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/tests/test_autoupgrade.py +0 -0
  25. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/tests/test_check_command.py +0 -0
  26. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/tests/test_expiry_warnings_and_revoke.py +0 -0
  27. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/tests/test_local_bind_commands.py +0 -0
  28. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/tests/test_runtime_credentials.py +0 -0
  29. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/tests/test_session_credentials.py +0 -0
  30. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/tests/test_silent_install.py +0 -0
  31. {forgexa_cli-1.76.0 → forgexa_cli-1.76.1}/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.76.0
3
+ Version: 1.76.1
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.76.0"
2
+ __version__ = "1.76.1"
@@ -55,7 +55,7 @@ import tempfile
55
55
  import time
56
56
  from contextlib import contextmanager
57
57
  from dataclasses import dataclass, field
58
- from pathlib import Path
58
+ from pathlib import Path, PurePosixPath
59
59
  from typing import Any
60
60
 
61
61
  # fcntl is Unix-only; on Windows we use msvcrt instead (see acquire_file_lock)
@@ -67,6 +67,42 @@ except ImportError:
67
67
  logger = logging.getLogger("agent_core")
68
68
 
69
69
 
70
+ def missing_planned_task_files(task_input: dict, changed_files: list[str]) -> list[str]:
71
+ """Find planned files without delivery evidence, including grouped sub-tasks."""
72
+ def normalize_path(value: object) -> str | None:
73
+ if not isinstance(value, str) or not value.strip():
74
+ return None
75
+ path = PurePosixPath(value.strip().replace("\\", "/"))
76
+ if path.is_absolute() or ".." in path.parts or not path.parts:
77
+ return None
78
+ return path.as_posix()
79
+
80
+ file_lists = [task_input.get("files")]
81
+ for item in task_input.get("items") or []:
82
+ if isinstance(item, dict):
83
+ file_lists.append(item.get("files"))
84
+ item_input = item.get("input")
85
+ if isinstance(item_input, dict):
86
+ file_lists.append(item_input.get("files"))
87
+ planned_paths = {
88
+ ((normalize_path(path) or path).rstrip("/") + "/")
89
+ if path.endswith("/") else (normalize_path(path) or path)
90
+ for paths in file_lists if isinstance(paths, list)
91
+ for path in paths if isinstance(path, str) and path.strip()
92
+ }
93
+ changed_paths = {
94
+ normalized for path in changed_files
95
+ if (normalized := normalize_path(path)) is not None
96
+ }
97
+ return sorted(
98
+ path for path in planned_paths
99
+ if not any(
100
+ changed == path or (path.endswith("/") and changed.startswith(path))
101
+ for changed in changed_paths
102
+ )
103
+ )
104
+
105
+
70
106
  _WORKSPACE_INSTRUCTION_PATHS = (
71
107
  "AGENTS.md",
72
108
  "CLAUDE.md",
@@ -1234,7 +1234,7 @@ except (ImportError, ModuleNotFoundError):
1234
1234
  # DAEMON_VERSION is the protocol/logic version of the daemon code.
1235
1235
  # Kept in sync with pyproject.toml version via bump-version.sh.
1236
1236
  # CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
1237
- DAEMON_VERSION = "1.76.0"
1237
+ DAEMON_VERSION = "1.76.1"
1238
1238
 
1239
1239
 
1240
1240
  def _detect_client_type() -> str:
@@ -6329,6 +6329,18 @@ class ProcessManager:
6329
6329
  return "unavailable"
6330
6330
  return "unable to run"
6331
6331
 
6332
+ @staticmethod
6333
+ def format_all_agents_failure(
6334
+ failure_kinds: list[str], attempts: list[tuple[str, str]],
6335
+ ) -> str:
6336
+ label = ProcessManager.all_agents_failure_label(failure_kinds)
6337
+ agent_order = ", ".join(agent_id for agent_id, _error in attempts)
6338
+ details = "\n".join(
6339
+ f"[{agent_id}] {error or 'Agent failed without an error message'}"
6340
+ for agent_id, error in attempts
6341
+ )
6342
+ return f"All agents {label} (tried: {agent_order}). Agent failures:\n{details}"
6343
+
6332
6344
  @staticmethod
6333
6345
  def is_idle_timeout_without_work(result: "TaskResult") -> bool:
6334
6346
  """Return whether an idle-killed agent left no work worth preserving.
@@ -7953,6 +7965,7 @@ class ProcessManager:
7953
7965
  disable_builtin_mcps: bool = False,
7954
7966
  _retry_without_effort: bool = False,
7955
7967
  _retry_without_token_env: bool = False,
7968
+ _retry_model_discovery_timeout: bool = False,
7956
7969
  _isolated_copilot_home: str | None = None,
7957
7970
  _auth_state: dict[str, bool] | None = None,
7958
7971
  ) -> TaskResult:
@@ -8219,12 +8232,40 @@ class ProcessManager:
8219
8232
  disable_builtin_mcps=True,
8220
8233
  _retry_without_effort=_retry_without_effort,
8221
8234
  _retry_without_token_env=_retry_without_token_env,
8235
+ _retry_model_discovery_timeout=_retry_model_discovery_timeout,
8222
8236
  _isolated_copilot_home=_isolated_copilot_home,
8223
8237
  _auth_state=_auth_state,
8224
8238
  )
8225
8239
  retry_result.metrics["copilot_builtin_mcp_fallback"] = True
8226
8240
  return retry_result
8227
8241
 
8242
+ model_discovery_detail = "\n".join([*structured_errors, stderr]).lower()
8243
+ if (
8244
+ "could not retrieve the list of available models" in model_discovery_detail
8245
+ and "timed out" in model_discovery_detail
8246
+ and not _retry_model_discovery_timeout
8247
+ and not (copilot_exit == 0 and returncode == 0)
8248
+ and not signals["has_meaningful_content"]
8249
+ and not self._copilot_called_any_tools(stdout)
8250
+ ):
8251
+ logger.warning(
8252
+ "Copilot model discovery timed out before work for task %s; "
8253
+ "retrying once with the same credentials and configuration",
8254
+ task_id,
8255
+ )
8256
+ retry_result = await self._run_copilot(
8257
+ agent, prompt, cwd, timeout, task_id, on_chunk,
8258
+ stream_stderr=stream_stderr,
8259
+ disable_builtin_mcps=disable_builtin_mcps,
8260
+ _retry_without_effort=_retry_without_effort,
8261
+ _retry_without_token_env=_retry_without_token_env,
8262
+ _retry_model_discovery_timeout=True,
8263
+ _isolated_copilot_home=_isolated_copilot_home,
8264
+ _auth_state=_auth_state,
8265
+ )
8266
+ retry_result.metrics["copilot_model_discovery_timeout_recovery"] = True
8267
+ return retry_result
8268
+
8228
8269
  model_discovery_error = agent_core.is_copilot_model_discovery_auth_error(
8229
8270
  *structured_errors,
8230
8271
  stderr,
@@ -8264,6 +8305,7 @@ class ProcessManager:
8264
8305
  disable_builtin_mcps=disable_builtin_mcps,
8265
8306
  _retry_without_effort=_retry_without_effort,
8266
8307
  _retry_without_token_env=True,
8308
+ _retry_model_discovery_timeout=_retry_model_discovery_timeout,
8267
8309
  _isolated_copilot_home=_isolated_copilot_home,
8268
8310
  _auth_state=_auth_state,
8269
8311
  )
@@ -8316,6 +8358,7 @@ class ProcessManager:
8316
8358
  disable_builtin_mcps=disable_builtin_mcps,
8317
8359
  _retry_without_effort=_retry_without_effort,
8318
8360
  _retry_without_token_env=True,
8361
+ _retry_model_discovery_timeout=_retry_model_discovery_timeout,
8319
8362
  _isolated_copilot_home=_isolated_copilot_home,
8320
8363
  _auth_state=_auth_state,
8321
8364
  )
@@ -8381,6 +8424,7 @@ class ProcessManager:
8381
8424
  disable_builtin_mcps=disable_builtin_mcps,
8382
8425
  _retry_without_effort=True,
8383
8426
  _retry_without_token_env=_retry_without_token_env,
8427
+ _retry_model_discovery_timeout=_retry_model_discovery_timeout,
8384
8428
  _isolated_copilot_home=_isolated_copilot_home,
8385
8429
  _auth_state=_auth_state,
8386
8430
  )
@@ -12877,6 +12921,7 @@ class RuntimeDaemon:
12877
12921
  else "timed out"
12878
12922
  )
12879
12923
  fallback_failure_kinds = [agent_failure_kind or fallback_reason] if fallback_reason else []
12924
+ fallback_attempt_errors = [(agent.agent_id, result.error)] if fallback_reason else []
12880
12925
  _skip_fallback = False
12881
12926
  if fallback_reason:
12882
12927
  _pre_fallback_git = await self.process_manager._collect_git_info(workspace_path)
@@ -12949,6 +12994,7 @@ class RuntimeDaemon:
12949
12994
  result = await self.process_manager.run_agent(
12950
12995
  agent, task, workspace_path, on_chunk=on_output_chunk,
12951
12996
  )
12997
+ fallback_attempt_errors.append((agent.agent_id, result.error))
12952
12998
  finally:
12953
12999
  progress_stop2.set()
12954
13000
  ticker2.cancel()
@@ -12989,12 +13035,8 @@ class RuntimeDaemon:
12989
13035
  )
12990
13036
 
12991
13037
  if ProcessManager.is_agent_fallback_eligible(result):
12992
- failure_label = ProcessManager.all_agents_failure_label(
12993
- fallback_failure_kinds,
12994
- )
12995
- result.error = (
12996
- f"All agents {failure_label} (tried: {', '.join(tried_agents)}). "
12997
- f"Original error: {result.error}"
13038
+ result.error = ProcessManager.format_all_agents_failure(
13039
+ fallback_failure_kinds, fallback_attempt_errors,
12998
13040
  )
12999
13041
  result.status = "failed"
13000
13042
  result.failure_code = ProcessManager.all_agents_failure_code(
@@ -13139,6 +13181,9 @@ class RuntimeDaemon:
13139
13181
  post_validation_failure_kinds = (
13140
13182
  [post_validation_failure_kind] if post_validation_failure_kind else []
13141
13183
  )
13184
+ post_validation_attempt_errors = (
13185
+ [(agent.agent_id, result.error)] if post_validation_failure_kind else []
13186
+ )
13142
13187
  post_validation_failure_label = (
13143
13188
  "rate-limited"
13144
13189
  if post_validation_failure_kind == "rate_limit"
@@ -13172,6 +13217,7 @@ class RuntimeDaemon:
13172
13217
  result = await self.process_manager.run_agent(
13173
13218
  agent, task, workspace_path, on_chunk=on_output_chunk,
13174
13219
  )
13220
+ post_validation_attempt_errors.append((agent.agent_id, result.error))
13175
13221
  fallback_failure_kind = ProcessManager.agent_failure_kind(result)
13176
13222
  if fallback_failure_kind:
13177
13223
  post_validation_failure_kinds.append(fallback_failure_kind)
@@ -13207,12 +13253,8 @@ class RuntimeDaemon:
13207
13253
  # If every agent we tried is still rate-limited, signal the server
13208
13254
  # NOT to re-enqueue — it would hit the same quota wall immediately.
13209
13255
  if ProcessManager.is_agent_fallback_eligible(result):
13210
- failure_label = ProcessManager.all_agents_failure_label(
13211
- post_validation_failure_kinds,
13212
- )
13213
- result.error = (
13214
- f"All agents {failure_label} (tried: {', '.join(tried_agents)}). "
13215
- f"Original error: {result.error}"
13256
+ result.error = ProcessManager.format_all_agents_failure(
13257
+ post_validation_failure_kinds, post_validation_attempt_errors,
13216
13258
  )
13217
13259
  result.status = "failed"
13218
13260
  result.failure_code = ProcessManager.all_agents_failure_code(
@@ -13810,6 +13852,16 @@ class RuntimeDaemon:
13810
13852
  req_type = input_data.get("requirement_type", "feature")
13811
13853
  verification_work_item = bool(input_data.get("verification_work_item"))
13812
13854
 
13855
+ if node_type == "coding":
13856
+ delivered_files = list(result.files_changed)
13857
+ delivered_files.extend(input_data.get("planned_task_predecessor_files") or [])
13858
+ for missing_path in agent_core.missing_planned_task_files(input_data, delivered_files):
13859
+ issues.append(
13860
+ f"Planned task file has no implementation evidence: {missing_path}. "
13861
+ "Complete the planned change or report the task as blocked; "
13862
+ "documentation-only changes do not complete a source-code task."
13863
+ )
13864
+
13813
13865
  qa_policy, qa_policy_issues = _parse_frozen_qa_test_level_policy(
13814
13866
  input_data.get("qa_test_level_policy_snapshot")
13815
13867
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.76.0
3
+ Version: 1.76.1
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.76.0"
3
+ version = "1.76.1"
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