forgexa-cli 1.22.2__tar.gz → 1.22.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.22.2 → forgexa_cli-1.22.4}/PKG-INFO +1 -1
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/forgexa_cli/daemon.py +224 -23
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/pyproject.toml +1 -1
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/README.md +0 -0
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/setup.cfg +0 -0
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.22.2 → forgexa_cli-1.22.4}/tests/test_upgrade_observability.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.22.
|
|
2
|
+
__version__ = "1.22.4"
|
|
@@ -555,7 +555,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
555
555
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
556
556
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
557
557
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
558
|
-
DAEMON_VERSION = "1.22.
|
|
558
|
+
DAEMON_VERSION = "1.22.4"
|
|
559
559
|
|
|
560
560
|
|
|
561
561
|
def _detect_client_type() -> str:
|
|
@@ -1012,6 +1012,8 @@ class TaskResult:
|
|
|
1012
1012
|
# Key values:
|
|
1013
1013
|
# "all_agents_rate_limited" — daemon tried every installed agent, all
|
|
1014
1014
|
# hit rate/quota limits. Server must NOT retry on the same runtime.
|
|
1015
|
+
# "validation_retry_exhausted" — required-output validation still failed
|
|
1016
|
+
# after the allowed repair agents. Server must not auto-retry it.
|
|
1015
1017
|
failure_code: str = ""
|
|
1016
1018
|
# Optional structured preflight result for project/runtime preflight failures.
|
|
1017
1019
|
preflight: dict | None = None
|
|
@@ -2615,7 +2617,10 @@ class WorkspaceManager:
|
|
|
2615
2617
|
|
|
2616
2618
|
async def _remove_broken_worktree(self, main_repo: Path, ws_path: Path, workspace_key: str):
|
|
2617
2619
|
"""Remove a broken worktree directory and clean up stale worktree refs."""
|
|
2618
|
-
#
|
|
2620
|
+
# `git worktree prune` keeps an entry while its directory exists, even
|
|
2621
|
+
# when that directory's .git pointer is corrupt.
|
|
2622
|
+
self._safe_rmtree(ws_path)
|
|
2623
|
+
|
|
2619
2624
|
if main_repo.exists():
|
|
2620
2625
|
try:
|
|
2621
2626
|
await self._git("worktree", "prune", cwd=main_repo)
|
|
@@ -2625,9 +2630,6 @@ class WorkspaceManager:
|
|
|
2625
2630
|
wt_ref = main_repo / ".git" / "worktrees" / workspace_key
|
|
2626
2631
|
if wt_ref.exists():
|
|
2627
2632
|
self._safe_rmtree(wt_ref)
|
|
2628
|
-
# Remove the broken worktree directory using _safe_rmtree so that
|
|
2629
|
-
# read-only git objects on Windows are properly handled.
|
|
2630
|
-
self._safe_rmtree(ws_path)
|
|
2631
2633
|
|
|
2632
2634
|
async def _safe_rmtree_main(self, main_repo: Path) -> None:
|
|
2633
2635
|
"""Remove _main repo, first evicting all linked worktrees to prevent orphans.
|
|
@@ -2675,8 +2677,10 @@ class WorkspaceManager:
|
|
|
2675
2677
|
self,
|
|
2676
2678
|
main_repo: Path,
|
|
2677
2679
|
branch_name: str,
|
|
2680
|
+
*,
|
|
2681
|
+
healthy_only: bool = True,
|
|
2678
2682
|
) -> Path | None:
|
|
2679
|
-
"""Return
|
|
2683
|
+
"""Return a linked worktree already on branch_name."""
|
|
2680
2684
|
try:
|
|
2681
2685
|
raw = await self._git("worktree", "list", "--porcelain", cwd=main_repo)
|
|
2682
2686
|
except RuntimeError:
|
|
@@ -2702,7 +2706,9 @@ class WorkspaceManager:
|
|
|
2702
2706
|
for candidate_path, candidate_branch in entries:
|
|
2703
2707
|
if candidate_path == main_repo or candidate_branch != target_ref:
|
|
2704
2708
|
continue
|
|
2705
|
-
if candidate_path.exists()
|
|
2709
|
+
if not candidate_path.exists():
|
|
2710
|
+
continue
|
|
2711
|
+
if not healthy_only or await self._is_healthy_worktree(candidate_path, main_repo=main_repo):
|
|
2706
2712
|
return candidate_path
|
|
2707
2713
|
|
|
2708
2714
|
return None
|
|
@@ -3825,6 +3831,43 @@ class WorkspaceManager:
|
|
|
3825
3831
|
cwd=main_repo,
|
|
3826
3832
|
)
|
|
3827
3833
|
except Exception as exc:
|
|
3834
|
+
existing_branch_worktree = await self._find_existing_worktree_for_branch(
|
|
3835
|
+
main_repo, branch_name,
|
|
3836
|
+
)
|
|
3837
|
+
if existing_branch_worktree is not None:
|
|
3838
|
+
logger.info(
|
|
3839
|
+
"Fresh start reusing worktree %s after resetting %s to origin/%s",
|
|
3840
|
+
existing_branch_worktree, branch_name, default_branch,
|
|
3841
|
+
)
|
|
3842
|
+
await self._git(
|
|
3843
|
+
"checkout", "-B", branch_name, f"origin/{default_branch}",
|
|
3844
|
+
cwd=existing_branch_worktree,
|
|
3845
|
+
)
|
|
3846
|
+
return existing_branch_worktree
|
|
3847
|
+
|
|
3848
|
+
stale_branch_worktree = await self._find_existing_worktree_for_branch(
|
|
3849
|
+
main_repo, branch_name, healthy_only=False,
|
|
3850
|
+
)
|
|
3851
|
+
if stale_branch_worktree is not None:
|
|
3852
|
+
logger.warning(
|
|
3853
|
+
"Fresh start found broken worktree %s holding branch %s; removing it before retrying",
|
|
3854
|
+
stale_branch_worktree, branch_name,
|
|
3855
|
+
)
|
|
3856
|
+
try:
|
|
3857
|
+
await self._remove_broken_worktree(
|
|
3858
|
+
main_repo, stale_branch_worktree, stale_branch_worktree.name,
|
|
3859
|
+
)
|
|
3860
|
+
await self._git(
|
|
3861
|
+
"branch", "-f", branch_name, f"origin/{default_branch}",
|
|
3862
|
+
cwd=main_repo,
|
|
3863
|
+
)
|
|
3864
|
+
await self._git(
|
|
3865
|
+
"worktree", "add", str(ws_path), branch_name,
|
|
3866
|
+
cwd=main_repo,
|
|
3867
|
+
)
|
|
3868
|
+
return ws_path
|
|
3869
|
+
except Exception as recovery_exc:
|
|
3870
|
+
exc = recovery_exc
|
|
3828
3871
|
self._safe_rmtree(ws_path)
|
|
3829
3872
|
raise RuntimeError(
|
|
3830
3873
|
f"Failed to create clean fresh worktree for branch '{branch_name}'. "
|
|
@@ -7692,6 +7735,22 @@ def _extract_testing_artifacts(workspace_path: Path, output_dir: str) -> dict |
|
|
|
7692
7735
|
return None
|
|
7693
7736
|
|
|
7694
7737
|
|
|
7738
|
+
def _case_evidence_entries(value: Any) -> list[dict[str, Any]]:
|
|
7739
|
+
"""Normalize list and keyed-object case_evidence forms into entries."""
|
|
7740
|
+
if isinstance(value, list):
|
|
7741
|
+
return [entry for entry in value if isinstance(entry, dict)]
|
|
7742
|
+
if isinstance(value, dict):
|
|
7743
|
+
entries: list[dict[str, Any]] = []
|
|
7744
|
+
for case_id, entry in value.items():
|
|
7745
|
+
if not isinstance(entry, dict):
|
|
7746
|
+
continue
|
|
7747
|
+
normalized = dict(entry)
|
|
7748
|
+
normalized.setdefault("test_case_id", str(case_id))
|
|
7749
|
+
entries.append(normalized)
|
|
7750
|
+
return entries
|
|
7751
|
+
return []
|
|
7752
|
+
|
|
7753
|
+
|
|
7695
7754
|
def _validate_test_evidence(
|
|
7696
7755
|
base: Path, workspace_path: Path, e2e_case_ids: list[str], max_anchor_checks: int = 50
|
|
7697
7756
|
) -> list[str]:
|
|
@@ -7725,12 +7784,13 @@ def _validate_test_evidence(
|
|
|
7725
7784
|
"must ground their selectors/routes in source-file evidence."
|
|
7726
7785
|
)
|
|
7727
7786
|
|
|
7728
|
-
case_evidence = ev.get("case_evidence")
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
if
|
|
7733
|
-
|
|
7787
|
+
case_evidence = _case_evidence_entries(ev.get("case_evidence"))
|
|
7788
|
+
entries_by_case_id: dict[str, list[dict[str, Any]]] = {}
|
|
7789
|
+
for entry in case_evidence:
|
|
7790
|
+
case_id = str(entry.get("test_case_id") or "").strip()
|
|
7791
|
+
if case_id:
|
|
7792
|
+
entries_by_case_id.setdefault(case_id, []).append(entry)
|
|
7793
|
+
covered_ids = set(entries_by_case_id)
|
|
7734
7794
|
missing_cases = [cid for cid in e2e_case_ids if cid not in covered_ids]
|
|
7735
7795
|
if missing_cases:
|
|
7736
7796
|
issues.append(
|
|
@@ -7739,6 +7799,54 @@ def _validate_test_evidence(
|
|
|
7739
7799
|
f"with route_refs/locator_refs."
|
|
7740
7800
|
)
|
|
7741
7801
|
|
|
7802
|
+
route_ids = {
|
|
7803
|
+
str(route.get("id"))
|
|
7804
|
+
for route in routes
|
|
7805
|
+
if isinstance(route, dict) and route.get("id")
|
|
7806
|
+
}
|
|
7807
|
+
locator_ids = {
|
|
7808
|
+
str(locator.get("id"))
|
|
7809
|
+
for locator in locators
|
|
7810
|
+
if isinstance(locator, dict) and locator.get("id")
|
|
7811
|
+
}
|
|
7812
|
+
for case_id in e2e_case_ids:
|
|
7813
|
+
entries = entries_by_case_id.get(case_id, [])
|
|
7814
|
+
if not entries:
|
|
7815
|
+
continue
|
|
7816
|
+
if len(entries) > 1:
|
|
7817
|
+
issues.append(
|
|
7818
|
+
f"test-evidence.json has multiple case_evidence entries for e2e test case "
|
|
7819
|
+
f"{case_id}. Keep exactly one entry per test case."
|
|
7820
|
+
)
|
|
7821
|
+
continue
|
|
7822
|
+
entry = entries[0]
|
|
7823
|
+
route_refs = entry.get("route_refs")
|
|
7824
|
+
locator_refs = entry.get("locator_refs")
|
|
7825
|
+
if not isinstance(route_refs, list) or not isinstance(locator_refs, list):
|
|
7826
|
+
issues.append(
|
|
7827
|
+
f"test-evidence.json case_evidence for {case_id} must define "
|
|
7828
|
+
"route_refs and locator_refs as arrays."
|
|
7829
|
+
)
|
|
7830
|
+
continue
|
|
7831
|
+
if not route_refs and not locator_refs:
|
|
7832
|
+
issues.append(
|
|
7833
|
+
f"test-evidence.json case_evidence for {case_id} must reference at least "
|
|
7834
|
+
"one route or locator."
|
|
7835
|
+
)
|
|
7836
|
+
continue
|
|
7837
|
+
unknown_routes = [str(ref) for ref in route_refs if str(ref) not in route_ids]
|
|
7838
|
+
unknown_locators = [str(ref) for ref in locator_refs if str(ref) not in locator_ids]
|
|
7839
|
+
if unknown_routes:
|
|
7840
|
+
issues.append(
|
|
7841
|
+
f"test-evidence.json case_evidence for {case_id} references unknown "
|
|
7842
|
+
f"route_refs: {unknown_routes[:10]}."
|
|
7843
|
+
)
|
|
7844
|
+
if unknown_locators:
|
|
7845
|
+
issues.append(
|
|
7846
|
+
f"test-evidence.json case_evidence for {case_id} references unknown "
|
|
7847
|
+
f"locator_refs: {unknown_locators[:10]}."
|
|
7848
|
+
)
|
|
7849
|
+
|
|
7742
7850
|
checked = 0
|
|
7743
7851
|
for item in list(locators) + list(routes):
|
|
7744
7852
|
if checked >= max_anchor_checks:
|
|
@@ -8969,9 +9077,9 @@ class RuntimeDaemon:
|
|
|
8969
9077
|
# 4.5 Layer 2: Validation gate — check outputs before committing
|
|
8970
9078
|
if result.status == "success":
|
|
8971
9079
|
try:
|
|
8972
|
-
result = await self.
|
|
9080
|
+
result, agent = await self._validate_with_agent_fallback(
|
|
8973
9081
|
agent, task, workspace_path, result,
|
|
8974
|
-
reporter, on_output_chunk,
|
|
9082
|
+
reporter, on_output_chunk, tried_agents=tried_agents,
|
|
8975
9083
|
default_branch=default_branch, before_sha=node_before_sha,
|
|
8976
9084
|
)
|
|
8977
9085
|
# Re-collect git info if validation triggered retries
|
|
@@ -9023,9 +9131,9 @@ class RuntimeDaemon:
|
|
|
9023
9131
|
# failure) — re-run the validation gate and update git state.
|
|
9024
9132
|
if result.status == "success":
|
|
9025
9133
|
try:
|
|
9026
|
-
result = await self.
|
|
9134
|
+
result, agent = await self._validate_with_agent_fallback(
|
|
9027
9135
|
agent, task, workspace_path, result,
|
|
9028
|
-
reporter, on_output_chunk,
|
|
9136
|
+
reporter, on_output_chunk, tried_agents=tried_agents,
|
|
9029
9137
|
default_branch=default_branch, before_sha=node_before_sha,
|
|
9030
9138
|
)
|
|
9031
9139
|
pre_commit_git = await self.process_manager._collect_git_info(workspace_path)
|
|
@@ -9230,7 +9338,11 @@ class RuntimeDaemon:
|
|
|
9230
9338
|
result.metrics["actual_agent"] = agent.agent_id
|
|
9231
9339
|
if agent.agent_id != task.agent_type:
|
|
9232
9340
|
result.metrics["original_agent"] = task.agent_type
|
|
9233
|
-
result.metrics["fallback_reason"] =
|
|
9341
|
+
result.metrics["fallback_reason"] = (
|
|
9342
|
+
result.metrics.get("fallback_reason")
|
|
9343
|
+
or fallback_reason
|
|
9344
|
+
or "validation_retry_exhausted"
|
|
9345
|
+
)
|
|
9234
9346
|
await reporter.report_progress(task.task_id, 100, "completed" if result.status == "success" else "failed")
|
|
9235
9347
|
await reporter.report_complete(task.task_id, result)
|
|
9236
9348
|
|
|
@@ -10069,6 +10181,78 @@ class RuntimeDaemon:
|
|
|
10069
10181
|
|
|
10070
10182
|
return info
|
|
10071
10183
|
|
|
10184
|
+
async def _validate_with_agent_fallback(
|
|
10185
|
+
self,
|
|
10186
|
+
agent: "DiscoveredAgent",
|
|
10187
|
+
task: TaskInfo,
|
|
10188
|
+
workspace_path: Path,
|
|
10189
|
+
result: TaskResult,
|
|
10190
|
+
reporter: "ProgressReporter",
|
|
10191
|
+
on_chunk: Any,
|
|
10192
|
+
tried_agents: set[str],
|
|
10193
|
+
default_branch: str = "",
|
|
10194
|
+
before_sha: str = "",
|
|
10195
|
+
) -> tuple[TaskResult, "DiscoveredAgent"]:
|
|
10196
|
+
"""Repair blocking validation failures without repeating an agent blindly."""
|
|
10197
|
+
tried_agents.add(agent.agent_id)
|
|
10198
|
+
result = await self._validate_and_retry(
|
|
10199
|
+
agent,
|
|
10200
|
+
task,
|
|
10201
|
+
workspace_path,
|
|
10202
|
+
result,
|
|
10203
|
+
reporter,
|
|
10204
|
+
on_chunk,
|
|
10205
|
+
max_retries=1,
|
|
10206
|
+
default_branch=default_branch,
|
|
10207
|
+
before_sha=before_sha,
|
|
10208
|
+
)
|
|
10209
|
+
if result.status != "failed" or result.failure_code != "validation_retry_exhausted":
|
|
10210
|
+
return result, agent
|
|
10211
|
+
|
|
10212
|
+
fallback_agent = self._select_fallback_agent(
|
|
10213
|
+
agent.agent_id,
|
|
10214
|
+
task.fallback_chain,
|
|
10215
|
+
tried_agents,
|
|
10216
|
+
)
|
|
10217
|
+
if not fallback_agent:
|
|
10218
|
+
result.metrics["validation_agents_tried"] = sorted(tried_agents)
|
|
10219
|
+
result.metrics["validation_agent_fallback_available"] = False
|
|
10220
|
+
result.error = (
|
|
10221
|
+
f"{result.error} No alternate agent is available on this runtime; "
|
|
10222
|
+
"retry after selecting a different agent or runtime."
|
|
10223
|
+
)
|
|
10224
|
+
return result, agent
|
|
10225
|
+
|
|
10226
|
+
tried_agents.add(fallback_agent.agent_id)
|
|
10227
|
+
await reporter.report_progress(
|
|
10228
|
+
task.task_id,
|
|
10229
|
+
92,
|
|
10230
|
+
f"validation_agent_fallback:{fallback_agent.agent_id}",
|
|
10231
|
+
output_lines=[
|
|
10232
|
+
"[daemon] Required-output validation still failed after the first repair, "
|
|
10233
|
+
f"switching from {agent.agent_id} to {fallback_agent.agent_id}.",
|
|
10234
|
+
],
|
|
10235
|
+
)
|
|
10236
|
+
previous_retries = int(result.metrics.get("validation_retries_used", 0) or 0)
|
|
10237
|
+
result = await self._validate_and_retry(
|
|
10238
|
+
fallback_agent,
|
|
10239
|
+
task,
|
|
10240
|
+
workspace_path,
|
|
10241
|
+
result,
|
|
10242
|
+
reporter,
|
|
10243
|
+
on_chunk,
|
|
10244
|
+
max_retries=1,
|
|
10245
|
+
default_branch=default_branch,
|
|
10246
|
+
before_sha=before_sha,
|
|
10247
|
+
)
|
|
10248
|
+
result.metrics["validation_agents_tried"] = sorted(tried_agents)
|
|
10249
|
+
result.metrics["validation_agent_fallback_available"] = True
|
|
10250
|
+
result.metrics["validation_retries_used"] = previous_retries + int(
|
|
10251
|
+
result.metrics.get("validation_retries_used", 0) or 0
|
|
10252
|
+
)
|
|
10253
|
+
result.metrics["fallback_reason"] = "validation_retry_exhausted"
|
|
10254
|
+
return result, fallback_agent
|
|
10255
|
+
|
|
10072
10256
|
async def _validate_and_retry(
|
|
10073
10257
|
self,
|
|
10074
10258
|
agent: "DiscoveredAgent",
|
|
@@ -10085,8 +10269,10 @@ class RuntimeDaemon:
|
|
|
10085
10269
|
|
|
10086
10270
|
Layer 2 of the reflection mechanism. Runs deterministic checks
|
|
10087
10271
|
(file existence, syntax, JSON validity) after agent completion but
|
|
10088
|
-
before git commit.
|
|
10089
|
-
|
|
10272
|
+
before git commit. If issues are found, builds a fix prompt listing all
|
|
10273
|
+
problems and gives the current agent one focused repair attempt. The
|
|
10274
|
+
caller routes a still-blocking failure to a different agent when one is
|
|
10275
|
+
available.
|
|
10090
10276
|
|
|
10091
10277
|
Returns the (possibly updated) TaskResult.
|
|
10092
10278
|
"""
|
|
@@ -10204,13 +10390,27 @@ class RuntimeDaemon:
|
|
|
10204
10390
|
)
|
|
10205
10391
|
remaining = self._validate_outputs(workspace_path, task, result)
|
|
10206
10392
|
if remaining:
|
|
10207
|
-
# Distinguish
|
|
10393
|
+
# Distinguish blocking artifact-contract failures from advisory syntax
|
|
10394
|
+
# warnings. Evidence coverage and references are release-blocking even
|
|
10395
|
+
# when the file itself exists.
|
|
10208
10396
|
scope_violations = [
|
|
10209
10397
|
issue
|
|
10210
10398
|
for issue in remaining
|
|
10211
10399
|
if issue.startswith("Verification work item modified requirement analysis assets:")
|
|
10212
10400
|
]
|
|
10213
|
-
critical_patterns = (
|
|
10401
|
+
critical_patterns = (
|
|
10402
|
+
"missing",
|
|
10403
|
+
"not found",
|
|
10404
|
+
"is empty",
|
|
10405
|
+
"not valid json",
|
|
10406
|
+
"case_evidence",
|
|
10407
|
+
"test-evidence.json has no",
|
|
10408
|
+
"evidence item",
|
|
10409
|
+
"anchor not found",
|
|
10410
|
+
"uncovered acceptance criteria",
|
|
10411
|
+
"no p0 priority test cases",
|
|
10412
|
+
"contains no test cases",
|
|
10413
|
+
)
|
|
10214
10414
|
critical_issues = [
|
|
10215
10415
|
iss for iss in remaining
|
|
10216
10416
|
if any(p in iss.lower() for p in critical_patterns)
|
|
@@ -10228,8 +10428,9 @@ class RuntimeDaemon:
|
|
|
10228
10428
|
result.failure_code = "verification_work_item_scope_violation"
|
|
10229
10429
|
result.error = "; ".join(scope_violations[:3])
|
|
10230
10430
|
else:
|
|
10431
|
+
result.failure_code = "validation_retry_exhausted"
|
|
10231
10432
|
result.error = (
|
|
10232
|
-
|
|
10433
|
+
"Agent failed to satisfy required output validation: "
|
|
10233
10434
|
+ "; ".join(critical_issues[:3])
|
|
10234
10435
|
)
|
|
10235
10436
|
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
|