forgexa-cli 1.83.0__tar.gz → 1.84.0__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.83.0 → forgexa_cli-1.84.0}/PKG-INFO +3 -3
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/README.md +2 -2
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli/agent_core.py +56 -5
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli/daemon.py +145 -70
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli.egg-info/PKG-INFO +3 -3
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/pyproject.toml +1 -1
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/tests/test_check_command.py +3 -3
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli/_local_bind.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli/aws_evidence.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli/aws_logs_evidence.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli/observability_contract.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli/observability_evidence.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli/runtime_evidence.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli/zcode_protocol.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/setup.cfg +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/tests/test_expiry_warnings_and_revoke.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/tests/test_local_bind_commands.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/tests/test_publish_script.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/tests/test_runtime_credentials.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/tests/test_session_credentials.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.83.0 → forgexa_cli-1.84.0}/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.
|
|
3
|
+
Version: 1.84.0
|
|
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
|
|
@@ -37,9 +37,9 @@ Requires-Dist: socksio>=1.0.0; extra == "daemon"
|
|
|
37
37
|
Command-line client and agent runtime for the [Forgexa](https://forgexa.net) platform.
|
|
38
38
|
|
|
39
39
|
Communicates with the Forgexa server via REST API.
|
|
40
|
-
Includes a built-in **daemon** with
|
|
40
|
+
Includes a built-in **daemon** with fourteen integrations: Claude Code, Codex, GitHub Copilot, Gemini CLI, OpenCode, Kimi Code, MiMo Code, Qwen Code, CodeBuddy, Qoder CLI, TraeCode CLI, ZCode, DeepSeek Harness, and MiniMax Code. Six standard candidates are automatically routed; eight require explicit selection. MiniMax Code and Harness require owned personal Runtimes/Desktop and local build/profile acceptance. Harness retains its exact 0.2.0-rc.2 and read-only external credential requirements. See [installation and limits](../documentation/guide/agent-installation.md).
|
|
41
41
|
|
|
42
|
-
MiniMax Code is
|
|
42
|
+
MiniMax Code is included in that count, supporting reviewed 0.7.0 with 0.6.13 compatibility. Run `forgexa agent minimax-init` to create an empty private profile, authenticate there, then use `forgexa agent minimax-status` and `forgexa agent approve-live minimax --evidence PATH` after real local acceptance. Personal credentials are never copied; Agent-only approval is refused. `forgexa check --agent minimax --quick` is non-billable; normal `check` runs a potentially billable task. See [MiniMax setup, evidence and recovery](../documentation/guide/agent-installation.md#minimax-code).
|
|
43
43
|
|
|
44
44
|
## Installation
|
|
45
45
|
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
Command-line client and agent runtime for the [Forgexa](https://forgexa.net) platform.
|
|
4
4
|
|
|
5
5
|
Communicates with the Forgexa server via REST API.
|
|
6
|
-
Includes a built-in **daemon** with
|
|
6
|
+
Includes a built-in **daemon** with fourteen integrations: Claude Code, Codex, GitHub Copilot, Gemini CLI, OpenCode, Kimi Code, MiMo Code, Qwen Code, CodeBuddy, Qoder CLI, TraeCode CLI, ZCode, DeepSeek Harness, and MiniMax Code. Six standard candidates are automatically routed; eight require explicit selection. MiniMax Code and Harness require owned personal Runtimes/Desktop and local build/profile acceptance. Harness retains its exact 0.2.0-rc.2 and read-only external credential requirements. See [installation and limits](../documentation/guide/agent-installation.md).
|
|
7
7
|
|
|
8
|
-
MiniMax Code is
|
|
8
|
+
MiniMax Code is included in that count, supporting reviewed 0.7.0 with 0.6.13 compatibility. Run `forgexa agent minimax-init` to create an empty private profile, authenticate there, then use `forgexa agent minimax-status` and `forgexa agent approve-live minimax --evidence PATH` after real local acceptance. Personal credentials are never copied; Agent-only approval is refused. `forgexa check --agent minimax --quick` is non-billable; normal `check` runs a potentially billable task. See [MiniMax setup, evidence and recovery](../documentation/guide/agent-installation.md#minimax-code).
|
|
9
9
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.
|
|
2
|
+
__version__ = "1.84.0"
|
|
@@ -67,6 +67,12 @@ except ImportError:
|
|
|
67
67
|
|
|
68
68
|
logger = logging.getLogger("agent_core")
|
|
69
69
|
|
|
70
|
+
IMPLEMENTATION_AUDIT_PROTOCOL_VERSION = 3
|
|
71
|
+
LOCAL_BIND_PRIVATE_VALIDATION_METRIC_KEYS = frozenset({
|
|
72
|
+
"validation_issues", "validation_issue_details", "timeout_recovery_validation_issues",
|
|
73
|
+
"timeout_recovery_original_failure", "task_delivery_issue",
|
|
74
|
+
})
|
|
75
|
+
|
|
70
76
|
|
|
71
77
|
def assess_approved_plan_conformance(baseline: dict, report: object) -> dict:
|
|
72
78
|
result = {
|
|
@@ -271,8 +277,41 @@ def _assess_implementation_contributions(contract: dict, report: object) -> dict
|
|
|
271
277
|
"files": [], "evidence": row.get("evidence") or [],
|
|
272
278
|
} for task_id in pending_verification_ids)
|
|
273
279
|
implementation_statuses = {row["task_id"]: row["status"] for row in checks}
|
|
280
|
+
deferred_contract = contract.get("deferred_acceptance_verification")
|
|
281
|
+
reserved_indexes = set()
|
|
282
|
+
if (
|
|
283
|
+
isinstance(deferred_contract, dict) and type(deferred_contract.get("version")) is int
|
|
284
|
+
and deferred_contract["version"] in (1, 2, 3)
|
|
285
|
+
and deferred_contract.get("execution_phase") == "verification_final"
|
|
286
|
+
and isinstance(deferred_contract.get("workflow_id"), str) and deferred_contract["workflow_id"]
|
|
287
|
+
and isinstance(deferred_contract.get("analysis_revision_id"), str) and deferred_contract["analysis_revision_id"]
|
|
288
|
+
and re.fullmatch(r"[0-9a-f]{64}", str(deferred_contract.get("qa_test_level_policy_fingerprint", "")))
|
|
289
|
+
):
|
|
290
|
+
reserved_ids = deferred_contract.get("acceptance_criteria_ids")
|
|
291
|
+
criteria = deferred_contract.get("criteria")
|
|
292
|
+
if (
|
|
293
|
+
isinstance(reserved_ids, list) and reserved_ids and all(isinstance(value, str) for value in reserved_ids)
|
|
294
|
+
and len(set(reserved_ids)) == len(reserved_ids) and isinstance(criteria, list)
|
|
295
|
+
and len(criteria) == len(reserved_ids)
|
|
296
|
+
and reserved_ids == [criterion.get("id") for criterion in criteria if isinstance(criterion, dict)]
|
|
297
|
+
):
|
|
298
|
+
for criterion_id in reserved_ids:
|
|
299
|
+
match = re.fullmatch(r"AC-([0-9]+)", criterion_id)
|
|
300
|
+
if match is None or not 1 <= int(match.group(1)) <= len(contract.get("acceptance_criteria") or []):
|
|
301
|
+
reserved_indexes.clear()
|
|
302
|
+
break
|
|
303
|
+
reserved_indexes.add(int(match.group(1)))
|
|
304
|
+
if len(reserved_indexes) != len(reserved_ids):
|
|
305
|
+
reserved_indexes.clear()
|
|
274
306
|
acceptance_checks = []
|
|
307
|
+
deferred_acceptance = []
|
|
275
308
|
for row in report["acceptance_checks"]:
|
|
309
|
+
if (
|
|
310
|
+
row["index"] in reserved_indexes and row["status"] == "unverified" and row.get("plan_revision") is None
|
|
311
|
+
and all(implementation_statuses.get(task_id) == "covered" for task_id in row["task_ids"])
|
|
312
|
+
):
|
|
313
|
+
deferred_acceptance.append(row["index"])
|
|
314
|
+
continue
|
|
276
315
|
if (
|
|
277
316
|
pending_verification_ids and row["status"] in ("blocked", "unverified")
|
|
278
317
|
and row.get("plan_revision") is None
|
|
@@ -285,7 +324,9 @@ def _assess_implementation_contributions(contract: dict, report: object) -> dict
|
|
|
285
324
|
"files": row.get("files") or [], "evidence": row["evidence"],
|
|
286
325
|
} for task_id in pending_verification_ids)
|
|
287
326
|
acceptance_checks.append(row)
|
|
288
|
-
result = assess_implementation_audit({
|
|
327
|
+
result = assess_implementation_audit({
|
|
328
|
+
**contract, "version": 1, "final_verification": contract.get("final_verification") and not deferred_acceptance,
|
|
329
|
+
}, {
|
|
289
330
|
**report, "task_checks": checks, "acceptance_checks": acceptance_checks,
|
|
290
331
|
})
|
|
291
332
|
result["report"] = report
|
|
@@ -293,6 +334,7 @@ def _assess_implementation_contributions(contract: dict, report: object) -> dict
|
|
|
293
334
|
result["report_valid"] = not result["issues"]
|
|
294
335
|
result["node_complete"] = result["node_complete"] and not issues
|
|
295
336
|
result["verification_pending"] = sorted({item["task_id"] for item in pending})
|
|
337
|
+
result["deferred_acceptance"] = sorted(deferred_acceptance)
|
|
296
338
|
result["findings"].extend(pending)
|
|
297
339
|
for request in original["revision_requests"]:
|
|
298
340
|
if request not in result["revision_requests"]:
|
|
@@ -302,7 +344,7 @@ def _assess_implementation_contributions(contract: dict, report: object) -> dict
|
|
|
302
344
|
if contract.get("final_verification") or request["task_id"] == contract.get("subject_task_id")
|
|
303
345
|
]
|
|
304
346
|
result["followup_task_ids"] = sorted({finding["task_id"] for finding in result["findings"]})
|
|
305
|
-
if issues or pending or result["revision_requests"]:
|
|
347
|
+
if issues or pending or result["revision_requests"] or deferred_acceptance:
|
|
306
348
|
result["status"] = "incomplete"
|
|
307
349
|
if result["report_valid"] and result["blocking_revision_requests"]:
|
|
308
350
|
result["status"] = "plan_revision_required"
|
|
@@ -312,7 +354,8 @@ def _assess_implementation_contributions(contract: dict, report: object) -> dict
|
|
|
312
354
|
|
|
313
355
|
def assess_implementation_audit(contract: dict, report: object) -> dict:
|
|
314
356
|
"""Validate implementation coverage independently of later runtime acceptance."""
|
|
315
|
-
|
|
357
|
+
version = contract.get("version", 1)
|
|
358
|
+
if type(version) is int and version == 2:
|
|
316
359
|
return _assess_implementation_contributions(contract, report)
|
|
317
360
|
issues: list[str] = []
|
|
318
361
|
findings: list[dict] = []
|
|
@@ -322,6 +365,9 @@ def assess_implementation_audit(contract: dict, report: object) -> dict:
|
|
|
322
365
|
"findings": findings, "followup_task_ids": [], "report": report,
|
|
323
366
|
"revision_requests": [], "blocking_reasons": [],
|
|
324
367
|
}
|
|
368
|
+
if type(version) is not int or version not in (1, 2):
|
|
369
|
+
issues.append("Implementation audit contract version is unsupported")
|
|
370
|
+
return result
|
|
325
371
|
if (
|
|
326
372
|
not isinstance(contract.get("audit_id"), str) or not contract["audit_id"]
|
|
327
373
|
or not isinstance(report, dict) or report.get("audit_id") != contract["audit_id"]
|
|
@@ -2681,6 +2727,7 @@ def _reject_nonfinite_json(value: str) -> None:
|
|
|
2681
2727
|
|
|
2682
2728
|
|
|
2683
2729
|
MINIMAX_MAX_OUTPUT_BYTES = 4 * 1024 * 1024
|
|
2730
|
+
MINIMAX_SUPPORTED_VERSIONS = ("0.6.13", "0.7.0")
|
|
2684
2731
|
MINIMAX_POLICY_VERSION = 1
|
|
2685
2732
|
MINIMAX_ACCEPTANCE_CHECKS = (
|
|
2686
2733
|
"start", "resume", "cancel", "timeout", "permissions", "filesystem",
|
|
@@ -2790,8 +2837,12 @@ def minimax_build_info(binary: str) -> dict:
|
|
|
2790
2837
|
if root is None or node is None or not Path(node).is_file():
|
|
2791
2838
|
raise RuntimeError("MiniMax requires a verifiable official package and Node installation")
|
|
2792
2839
|
metadata = json.loads((root / "package.json").read_text(encoding="utf-8"))
|
|
2793
|
-
if metadata.get("version")
|
|
2794
|
-
raise RuntimeError(
|
|
2840
|
+
if metadata.get("version") not in MINIMAX_SUPPORTED_VERSIONS:
|
|
2841
|
+
raise RuntimeError(
|
|
2842
|
+
"MiniMax Code requires a protocol-reviewed version ("
|
|
2843
|
+
+ ", ".join(MINIMAX_SUPPORTED_VERSIONS)
|
|
2844
|
+
+ "); review other builds before use"
|
|
2845
|
+
)
|
|
2795
2846
|
env = deepseek_harness_probe_env()
|
|
2796
2847
|
version = subprocess.run([node, "-p", "process.versions.node"], capture_output=True,
|
|
2797
2848
|
text=True, timeout=10, env=env)
|
|
@@ -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.
|
|
1237
|
+
DAEMON_VERSION = "1.84.0"
|
|
1238
1238
|
|
|
1239
1239
|
|
|
1240
1240
|
def _detect_client_type() -> str:
|
|
@@ -1772,11 +1772,17 @@ def _build_runtime_critic_prompt(
|
|
|
1772
1772
|
]
|
|
1773
1773
|
parts.extend((
|
|
1774
1774
|
"## Approved Responsibility Boundaries", json.dumps(responsibilities, ensure_ascii=True, sort_keys=True),
|
|
1775
|
+
"This is the designated final completeness review: recheck every approved task and criterion "
|
|
1776
|
+
"assigned to this phase, including other contributors' unresolved source obligations. Only an "
|
|
1777
|
+
"explicit server-owned deferred verification contract reserves later execution; required "
|
|
1778
|
+
"prepared assets and in-scope checks remain mandatory."
|
|
1779
|
+
if audit_contract.get("final_verification") else
|
|
1775
1780
|
"The feature's acceptance criteria remain mandatory, but they are not all obligations of this node. "
|
|
1776
1781
|
"Assess this task's approved responsibility and required in-scope checks. Execution and sign-off "
|
|
1777
1782
|
"assigned to other approved tasks stay pending under those owners; their absence alone does not "
|
|
1778
1783
|
"invalidate a complete source contribution. A task that owns runtime verification still requires "
|
|
1779
|
-
"that evidence.
|
|
1784
|
+
"that evidence.",
|
|
1785
|
+
"Independently inspect committed source and checks; implementer claims are not "
|
|
1780
1786
|
"independent proof. Missing assigned source, actual failed checks and genuine deviations remain blocking. "
|
|
1781
1787
|
"For unverified, name the exact assigned obligation and the unavailable source or check evidence, "
|
|
1782
1788
|
"rather than substituting the entire feature's unexecuted acceptance for this task's review.",
|
|
@@ -1879,6 +1885,56 @@ def _format_reflection_findings(findings: object) -> str:
|
|
|
1879
1885
|
return "\n".join(lines)
|
|
1880
1886
|
|
|
1881
1887
|
|
|
1888
|
+
class _OutputValidationIssue(str):
|
|
1889
|
+
code: str
|
|
1890
|
+
severity: str
|
|
1891
|
+
|
|
1892
|
+
def __new__(cls, code: str, message: str, severity: str = "error") -> "_OutputValidationIssue":
|
|
1893
|
+
issue = super().__new__(cls, message)
|
|
1894
|
+
issue.code = code
|
|
1895
|
+
issue.severity = severity
|
|
1896
|
+
return issue
|
|
1897
|
+
|
|
1898
|
+
|
|
1899
|
+
def _output_validation_issue(issue: str) -> _OutputValidationIssue:
|
|
1900
|
+
if isinstance(issue, _OutputValidationIssue):
|
|
1901
|
+
return issue
|
|
1902
|
+
for prefix, code in (
|
|
1903
|
+
("Planned task file has no implementation evidence:", "planned_task_incomplete"),
|
|
1904
|
+
("Verification work item modified requirement analysis assets:", "verification_work_item_scope_violation"),
|
|
1905
|
+
("Uncovered acceptance criteria in coverage-matrix.json:", "acceptance_criteria_uncovered"),
|
|
1906
|
+
):
|
|
1907
|
+
if issue.startswith(prefix):
|
|
1908
|
+
return _OutputValidationIssue(code, issue)
|
|
1909
|
+
if issue.startswith("qa_policy."):
|
|
1910
|
+
return _OutputValidationIssue(issue.partition(":")[0], issue)
|
|
1911
|
+
if issue.startswith("Possible syntax issue in "):
|
|
1912
|
+
return _OutputValidationIssue("syntax_advisory", issue, "warning")
|
|
1913
|
+
return _OutputValidationIssue("output_validation_failed", issue)
|
|
1914
|
+
|
|
1915
|
+
|
|
1916
|
+
def _record_output_validation_issues(result: "TaskResult", issues: list[str]) -> None:
|
|
1917
|
+
result.metrics["validation_issues"] = [str(issue) for issue in issues]
|
|
1918
|
+
result.metrics["validation_issue_details"] = [
|
|
1919
|
+
{"code": normalized.code, "severity": normalized.severity, "message": str(normalized)}
|
|
1920
|
+
for issue in issues for normalized in [_output_validation_issue(issue)]
|
|
1921
|
+
]
|
|
1922
|
+
|
|
1923
|
+
|
|
1924
|
+
def _mark_output_validation_unavailable(result: "TaskResult", snapshot: object = None) -> None:
|
|
1925
|
+
if result.status == "failed" and result.failure_code:
|
|
1926
|
+
return
|
|
1927
|
+
if _qa_source_test_validation_required(snapshot):
|
|
1928
|
+
_mark_qa_source_validation_unavailable(result)
|
|
1929
|
+
return
|
|
1930
|
+
result.status = "failed"
|
|
1931
|
+
result.failure_code = "validation_unavailable"
|
|
1932
|
+
result.error = "Output validation could not complete; inspect the runtime validation log before retrying."
|
|
1933
|
+
_record_output_validation_issues(result, [
|
|
1934
|
+
_OutputValidationIssue("validation_unavailable", result.error),
|
|
1935
|
+
])
|
|
1936
|
+
|
|
1937
|
+
|
|
1882
1938
|
def _filter_local_bind_task_result(result: "TaskResult") -> None:
|
|
1883
1939
|
"""Strip raw local-environment output from ordinary completion fields.
|
|
1884
1940
|
|
|
@@ -1892,7 +1948,8 @@ def _filter_local_bind_task_result(result: "TaskResult") -> None:
|
|
|
1892
1948
|
result.stdout = ""
|
|
1893
1949
|
result.stderr = ""
|
|
1894
1950
|
result.observations = []
|
|
1895
|
-
|
|
1951
|
+
for key in agent_core.LOCAL_BIND_PRIVATE_VALIDATION_METRIC_KEYS:
|
|
1952
|
+
result.metrics.pop(key, None)
|
|
1896
1953
|
critic_review = result.critic_review
|
|
1897
1954
|
if isinstance(critic_review, dict) and isinstance(critic_review.get("result"), dict):
|
|
1898
1955
|
result.critic_review = {
|
|
@@ -6692,6 +6749,7 @@ class ProcessManager:
|
|
|
6692
6749
|
|
|
6693
6750
|
elapsed = time.monotonic() - start_time
|
|
6694
6751
|
result.metrics["duration_seconds"] = round(elapsed, 2)
|
|
6752
|
+
result.metrics["actual_agent"] = agent.agent_id
|
|
6695
6753
|
|
|
6696
6754
|
# Detect agent-level failures despite exit code 0
|
|
6697
6755
|
output_failure = None if agent_core.canonical_agent_id(agent.agent_id) in {"deepseek", "minimax"} else self._detect_agent_output_failure(result, agent.agent_id)
|
|
@@ -10022,7 +10080,7 @@ class ServerConnection:
|
|
|
10022
10080
|
"acceptance_verification": {
|
|
10023
10081
|
"protocol_version": runtime_evidence.DEFERRED_ACCEPTANCE_VERIFICATION_PROTOCOL_VERSION,
|
|
10024
10082
|
},
|
|
10025
|
-
"implementation_audit": {"protocol_version":
|
|
10083
|
+
"implementation_audit": {"protocol_version": agent_core.IMPLEMENTATION_AUDIT_PROTOCOL_VERSION},
|
|
10026
10084
|
"task_delivery": {"protocol_version": 1},
|
|
10027
10085
|
"recording_suggestions": {"protocol_version": 1, "isolated_workspace": True},
|
|
10028
10086
|
"task_attachment_staging": {"protocol_version": 1, "runtime_proxy": True, "image_agents": ["claude"]},
|
|
@@ -10448,7 +10506,7 @@ class ServerConnection:
|
|
|
10448
10506
|
"acceptance_verification": {
|
|
10449
10507
|
"protocol_version": runtime_evidence.DEFERRED_ACCEPTANCE_VERIFICATION_PROTOCOL_VERSION,
|
|
10450
10508
|
},
|
|
10451
|
-
"implementation_audit": {"protocol_version":
|
|
10509
|
+
"implementation_audit": {"protocol_version": agent_core.IMPLEMENTATION_AUDIT_PROTOCOL_VERSION},
|
|
10452
10510
|
"task_delivery": {"protocol_version": 1},
|
|
10453
10511
|
"recording_suggestions": {"protocol_version": 1, "isolated_workspace": True},
|
|
10454
10512
|
"local_evidence": {"protocol_version": 1, "structured_summary": True},
|
|
@@ -12480,6 +12538,9 @@ class RuntimeDaemon:
|
|
|
12480
12538
|
):
|
|
12481
12539
|
return False
|
|
12482
12540
|
|
|
12541
|
+
result.metrics["timeout_recovery_original_failure"] = {
|
|
12542
|
+
"status": result.status, "failure_code": result.failure_code, "error": result.error,
|
|
12543
|
+
}
|
|
12483
12544
|
validation_issues = self._validate_outputs(workspace_path, task, result)
|
|
12484
12545
|
if validation_issues:
|
|
12485
12546
|
result.metrics["timeout_recovery_validation_issues"] = validation_issues
|
|
@@ -13365,17 +13426,14 @@ class RuntimeDaemon:
|
|
|
13365
13426
|
reporter, on_output_chunk, tried_agents=tried_agents,
|
|
13366
13427
|
default_branch=default_branch, before_sha=delivery_before_sha,
|
|
13367
13428
|
)
|
|
13368
|
-
# Re-collect
|
|
13369
|
-
|
|
13370
|
-
pre_commit_git = await self.process_manager._collect_git_info(workspace_path)
|
|
13429
|
+
# Re-collect after validation, including a successful repair.
|
|
13430
|
+
pre_commit_git = await self.process_manager._collect_git_info(workspace_path)
|
|
13371
13431
|
except Exception:
|
|
13372
|
-
|
|
13432
|
+
_mark_output_validation_unavailable(
|
|
13433
|
+
result,
|
|
13373
13434
|
(task.input_data or {}).get("qa_test_level_policy_snapshot")
|
|
13374
|
-
)
|
|
13375
|
-
|
|
13376
|
-
logger.exception("Validation gate error for task %s (refusing to proceed)", task.task_id)
|
|
13377
|
-
else:
|
|
13378
|
-
logger.exception("Validation gate error for task %s (proceeding anyway)", task.task_id)
|
|
13435
|
+
)
|
|
13436
|
+
logger.exception("Validation gate error for task %s (refusing to proceed)", task.task_id)
|
|
13379
13437
|
|
|
13380
13438
|
if result.failure_code == "verification_work_item_scope_violation" and not is_local_bind:
|
|
13381
13439
|
await self._discard_verification_scope_changes(
|
|
@@ -13449,8 +13507,11 @@ class RuntimeDaemon:
|
|
|
13449
13507
|
)
|
|
13450
13508
|
pre_commit_git = await self.process_manager._collect_git_info(workspace_path)
|
|
13451
13509
|
except Exception:
|
|
13510
|
+
_mark_output_validation_unavailable(
|
|
13511
|
+
result, (task.input_data or {}).get("qa_test_level_policy_snapshot"),
|
|
13512
|
+
)
|
|
13452
13513
|
logger.exception(
|
|
13453
|
-
"Post-validation gate error for task %s (
|
|
13514
|
+
"Post-validation gate error for task %s (refusing to proceed)",
|
|
13454
13515
|
task.task_id,
|
|
13455
13516
|
)
|
|
13456
13517
|
break
|
|
@@ -14210,9 +14271,10 @@ class RuntimeDaemon:
|
|
|
14210
14271
|
result.status = "failed"
|
|
14211
14272
|
result.failure_code = "approved_plan_baseline_modified"
|
|
14212
14273
|
result.error = "Implementation modified frozen analysis assets: " + ", ".join(forbidden_paths[:5])
|
|
14213
|
-
issues.append(result.error)
|
|
14274
|
+
issues.append(_OutputValidationIssue(result.failure_code, result.error))
|
|
14214
14275
|
|
|
14215
14276
|
audit_contract = input_data.get("implementation_audit_contract")
|
|
14277
|
+
result.metrics.pop("implementation_audit_node_complete", None)
|
|
14216
14278
|
if isinstance(audit_contract, dict):
|
|
14217
14279
|
agent_id = str(result.metrics.get("actual_agent") or getattr(task, "agent_type", "") or "")
|
|
14218
14280
|
audit_report = agent_core.extract_implementation_audit(_daemon_extract_agent_output(agent_id, result.stdout))
|
|
@@ -14223,6 +14285,9 @@ class RuntimeDaemon:
|
|
|
14223
14285
|
if isinstance(audit_report, dict):
|
|
14224
14286
|
result.observations.append({"type": "implementation_audit", "data": audit_report})
|
|
14225
14287
|
audit_result = agent_core.assess_implementation_audit(audit_contract, audit_report)
|
|
14288
|
+
result.metrics["implementation_audit_node_complete"] = bool(
|
|
14289
|
+
audit_result["report_valid"] and audit_result["node_complete"]
|
|
14290
|
+
)
|
|
14226
14291
|
if audit_result["report_valid"] and audit_result["status"] == "plan_revision_required":
|
|
14227
14292
|
result.status = "failed"
|
|
14228
14293
|
result.failure_code = "implementation_plan_revision_required"
|
|
@@ -14230,11 +14295,12 @@ class RuntimeDaemon:
|
|
|
14230
14295
|
result.error = "Approved analysis and task plan require revision before implementation can continue: " + "; ".join(
|
|
14231
14296
|
f"{request['task_id']}: {request['reason']}" for request in requests[:3]
|
|
14232
14297
|
)
|
|
14233
|
-
issues.append(result.error)
|
|
14298
|
+
issues.append(_OutputValidationIssue(result.failure_code, result.error))
|
|
14234
14299
|
elif not audit_result["report_valid"] or not audit_result["node_complete"]:
|
|
14235
|
-
issues.extend(
|
|
14300
|
+
issues.extend(_OutputValidationIssue("implementation_audit_incomplete", issue) for issue in (
|
|
14301
|
+
audit_result["issues"] or audit_result.get("blocking_reasons") or [
|
|
14236
14302
|
"The selected approved task still has implementation gaps",
|
|
14237
|
-
])
|
|
14303
|
+
]))
|
|
14238
14304
|
|
|
14239
14305
|
if node_type == "coding" or (
|
|
14240
14306
|
node_type == "fix" and input_data.get("task") == "phase_remediation_fix"
|
|
@@ -14242,11 +14308,12 @@ class RuntimeDaemon:
|
|
|
14242
14308
|
delivered_files = list(result.files_changed)
|
|
14243
14309
|
delivered_files.extend(input_data.get("planned_task_predecessor_files") or [])
|
|
14244
14310
|
for missing_path in agent_core.missing_planned_task_files(input_data, delivered_files):
|
|
14245
|
-
issues.append(
|
|
14311
|
+
issues.append(_OutputValidationIssue(
|
|
14312
|
+
"planned_task_incomplete",
|
|
14246
14313
|
f"Planned task file has no implementation evidence: {missing_path}. "
|
|
14247
14314
|
"Complete the planned change or report the task as blocked; "
|
|
14248
14315
|
"documentation-only changes do not complete a source-code task."
|
|
14249
|
-
)
|
|
14316
|
+
))
|
|
14250
14317
|
|
|
14251
14318
|
qa_policy, qa_policy_issues = _parse_frozen_qa_test_level_policy(
|
|
14252
14319
|
input_data.get("qa_test_level_policy_snapshot")
|
|
@@ -14266,10 +14333,11 @@ class RuntimeDaemon:
|
|
|
14266
14333
|
if _is_requirement_analysis_path(path)
|
|
14267
14334
|
)
|
|
14268
14335
|
if forbidden_paths:
|
|
14269
|
-
issues.append(
|
|
14336
|
+
issues.append(_OutputValidationIssue(
|
|
14337
|
+
"verification_work_item_scope_violation",
|
|
14270
14338
|
"Verification work item modified requirement analysis assets: "
|
|
14271
14339
|
+ ", ".join(forbidden_paths[:5])
|
|
14272
|
-
)
|
|
14340
|
+
))
|
|
14273
14341
|
|
|
14274
14342
|
if verification_work_item and node_type == "analysis":
|
|
14275
14343
|
analysis_doc_path = str(input_data.get("verification_analysis_doc_path", "") or "")
|
|
@@ -14425,24 +14493,29 @@ class RuntimeDaemon:
|
|
|
14425
14493
|
# Syntax-check modified Python and JS/TS files
|
|
14426
14494
|
for f in result.files_changed:
|
|
14427
14495
|
fpath = workspace_path / f
|
|
14428
|
-
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
|
|
14496
|
+
try:
|
|
14497
|
+
if not fpath.exists():
|
|
14498
|
+
continue
|
|
14499
|
+
if f.endswith(".py"):
|
|
14432
14500
|
import ast
|
|
14433
|
-
ast.parse(fpath.
|
|
14434
|
-
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14445
|
-
)
|
|
14501
|
+
ast.parse(fpath.read_bytes(), filename=f)
|
|
14502
|
+
elif f.endswith((".js", ".ts", ".jsx", ".tsx")):
|
|
14503
|
+
content = fpath.read_text(encoding="utf-8")
|
|
14504
|
+
opens = content.count("{") + content.count("(") + content.count("[")
|
|
14505
|
+
closes = content.count("}") + content.count(")") + content.count("]")
|
|
14506
|
+
if abs(opens - closes) > 2:
|
|
14507
|
+
issues.append(_OutputValidationIssue(
|
|
14508
|
+
"syntax_advisory", f"Possible syntax issue in {f}: "
|
|
14509
|
+
f"bracket imbalance (open={opens}, close={closes})", "warning",
|
|
14510
|
+
))
|
|
14511
|
+
except SyntaxError as error:
|
|
14512
|
+
issues.append(_OutputValidationIssue(
|
|
14513
|
+
"source_syntax_invalid", f"Python syntax error in {f}: {error.msg} (line {error.lineno})",
|
|
14514
|
+
))
|
|
14515
|
+
except (OSError, UnicodeError):
|
|
14516
|
+
issues.append(_OutputValidationIssue(
|
|
14517
|
+
"validation_unavailable", f"Output validation could not read source file: {f}",
|
|
14518
|
+
))
|
|
14446
14519
|
|
|
14447
14520
|
# Task 6 (P6 fix): warning-level phase_handoffs check for non-analysis
|
|
14448
14521
|
# nodes. These nodes SHOULD append a phase_handoffs entry to analysis.json
|
|
@@ -14767,10 +14840,11 @@ class RuntimeDaemon:
|
|
|
14767
14840
|
ids = ", ".join(ac["id"] for ac in uncovered[:20])
|
|
14768
14841
|
if len(uncovered) > 20:
|
|
14769
14842
|
ids += f" (+{len(uncovered) - 20} more)"
|
|
14770
|
-
issues.append(
|
|
14843
|
+
issues.append(_OutputValidationIssue(
|
|
14844
|
+
"acceptance_criteria_uncovered",
|
|
14771
14845
|
"Uncovered acceptance criteria in "
|
|
14772
14846
|
f"coverage-matrix.json: {ids}"
|
|
14773
|
-
)
|
|
14847
|
+
))
|
|
14774
14848
|
except (_json.JSONDecodeError, UnicodeDecodeError) as e:
|
|
14775
14849
|
issues.append(f"coverage-matrix.json is not valid JSON: {e}")
|
|
14776
14850
|
elif (
|
|
@@ -16214,12 +16288,18 @@ class RuntimeDaemon:
|
|
|
16214
16288
|
before_sha=before_sha,
|
|
16215
16289
|
preserved_paths=preserved_paths,
|
|
16216
16290
|
)
|
|
16217
|
-
issues = self._validate_outputs(workspace_path, task, result)
|
|
16291
|
+
issues = [_output_validation_issue(issue) for issue in self._validate_outputs(workspace_path, task, result)]
|
|
16218
16292
|
if result.failure_code in ("implementation_plan_revision_required", "approved_plan_baseline_modified"):
|
|
16293
|
+
_record_output_validation_issues(result, issues)
|
|
16294
|
+
logger.warning("Output validation stopped task %s (%s)", task.task_id, result.failure_code)
|
|
16219
16295
|
return result
|
|
16220
16296
|
if require_source_inventory:
|
|
16221
|
-
issues.extend(inventory_issues)
|
|
16297
|
+
issues.extend(_output_validation_issue(issue) for issue in inventory_issues)
|
|
16298
|
+
advisory_issues = [issue for issue in issues if issue.severity == "warning"]
|
|
16299
|
+
issues = [issue for issue in issues if issue.severity != "warning"]
|
|
16222
16300
|
if not issues:
|
|
16301
|
+
if advisory_issues:
|
|
16302
|
+
_record_output_validation_issues(result, advisory_issues)
|
|
16223
16303
|
self._record_testing_telemetry(
|
|
16224
16304
|
workspace_path, task, result, retries_used=attempt
|
|
16225
16305
|
)
|
|
@@ -16234,7 +16314,7 @@ class RuntimeDaemon:
|
|
|
16234
16314
|
if (
|
|
16235
16315
|
agent_core.is_implementation_testing_task(task.node_type, task.input_data or {})
|
|
16236
16316
|
and all(
|
|
16237
|
-
issue.
|
|
16317
|
+
issue.code == "acceptance_criteria_uncovered"
|
|
16238
16318
|
for issue in issues
|
|
16239
16319
|
)
|
|
16240
16320
|
):
|
|
@@ -16245,7 +16325,7 @@ class RuntimeDaemon:
|
|
|
16245
16325
|
"Resolve the unmet criteria or missing test prerequisites, "
|
|
16246
16326
|
"then rerun verification."
|
|
16247
16327
|
)
|
|
16248
|
-
result
|
|
16328
|
+
_record_output_validation_issues(result, issues)
|
|
16249
16329
|
self._record_testing_telemetry(
|
|
16250
16330
|
workspace_path, task, result, retries_used=attempt
|
|
16251
16331
|
)
|
|
@@ -16343,11 +16423,13 @@ class RuntimeDaemon:
|
|
|
16343
16423
|
before_sha=before_sha,
|
|
16344
16424
|
preserved_paths=preserved_paths,
|
|
16345
16425
|
)
|
|
16346
|
-
remaining = self._validate_outputs(workspace_path, task, result)
|
|
16426
|
+
remaining = [_output_validation_issue(issue) for issue in self._validate_outputs(workspace_path, task, result)]
|
|
16347
16427
|
if result.failure_code in ("implementation_plan_revision_required", "approved_plan_baseline_modified"):
|
|
16428
|
+
_record_output_validation_issues(result, remaining)
|
|
16429
|
+
logger.warning("Output validation stopped task %s (%s)", task.task_id, result.failure_code)
|
|
16348
16430
|
return result
|
|
16349
16431
|
if require_source_inventory:
|
|
16350
|
-
remaining.extend(inventory_issues)
|
|
16432
|
+
remaining.extend(_output_validation_issue(issue) for issue in inventory_issues)
|
|
16351
16433
|
if remaining:
|
|
16352
16434
|
if getattr(task, "workspace_resolution", None) == "local_bind":
|
|
16353
16435
|
_append_local_bind_output_log(task.task_id, [
|
|
@@ -16357,7 +16439,7 @@ class RuntimeDaemon:
|
|
|
16357
16439
|
if (
|
|
16358
16440
|
agent_core.is_implementation_testing_task(task.node_type, task.input_data or {})
|
|
16359
16441
|
and all(
|
|
16360
|
-
issue.
|
|
16442
|
+
issue.code == "acceptance_criteria_uncovered"
|
|
16361
16443
|
for issue in remaining
|
|
16362
16444
|
)
|
|
16363
16445
|
):
|
|
@@ -16368,7 +16450,7 @@ class RuntimeDaemon:
|
|
|
16368
16450
|
"Resolve the unmet criteria or missing test prerequisites, "
|
|
16369
16451
|
"then rerun verification."
|
|
16370
16452
|
)
|
|
16371
|
-
result
|
|
16453
|
+
_record_output_validation_issues(result, remaining)
|
|
16372
16454
|
self._record_testing_telemetry(
|
|
16373
16455
|
workspace_path, task, result, retries_used=max_retries
|
|
16374
16456
|
)
|
|
@@ -16379,35 +16461,22 @@ class RuntimeDaemon:
|
|
|
16379
16461
|
scope_violations = [
|
|
16380
16462
|
issue
|
|
16381
16463
|
for issue in remaining
|
|
16382
|
-
if issue.
|
|
16464
|
+
if issue.code == "verification_work_item_scope_violation"
|
|
16383
16465
|
]
|
|
16384
|
-
critical_patterns = (
|
|
16385
|
-
"missing",
|
|
16386
|
-
"not found",
|
|
16387
|
-
"is empty",
|
|
16388
|
-
"not valid json",
|
|
16389
|
-
"invalid acceptance criteria",
|
|
16390
|
-
"case_evidence",
|
|
16391
|
-
"test-evidence.json has no",
|
|
16392
|
-
"evidence item",
|
|
16393
|
-
"anchor not found",
|
|
16394
|
-
"uncovered acceptance criteria",
|
|
16395
|
-
"no p0 priority test cases",
|
|
16396
|
-
"contains no test cases",
|
|
16397
|
-
)
|
|
16398
16466
|
critical_issues = [
|
|
16399
|
-
|
|
16400
|
-
if any(p in iss.lower() for p in critical_patterns)
|
|
16467
|
+
issue for issue in remaining if issue.severity != "warning"
|
|
16401
16468
|
]
|
|
16402
16469
|
qa_policy_violations = [
|
|
16403
|
-
issue for issue in remaining if issue.startswith("qa_policy.")
|
|
16470
|
+
issue for issue in remaining if issue.code.startswith("qa_policy.")
|
|
16404
16471
|
]
|
|
16405
16472
|
planned_delivery_issues = [
|
|
16406
16473
|
issue for issue in remaining
|
|
16407
|
-
if issue.
|
|
16474
|
+
if issue.code == "planned_task_incomplete"
|
|
16408
16475
|
]
|
|
16476
|
+
audit_incomplete = result.metrics.get("implementation_audit_node_complete") is False
|
|
16409
16477
|
blocking_issues = list(dict.fromkeys(
|
|
16410
16478
|
critical_issues + scope_violations + qa_policy_violations + planned_delivery_issues
|
|
16479
|
+
+ (remaining if audit_incomplete else [])
|
|
16411
16480
|
))
|
|
16412
16481
|
if blocking_issues:
|
|
16413
16482
|
# Agent didn't produce required output — mark as failed
|
|
@@ -16424,6 +16493,12 @@ class RuntimeDaemon:
|
|
|
16424
16493
|
elif planned_delivery_issues:
|
|
16425
16494
|
result.failure_code = "planned_task_incomplete"
|
|
16426
16495
|
result.error = "; ".join(planned_delivery_issues[:3])
|
|
16496
|
+
elif any(issue.code == "validation_unavailable" for issue in remaining):
|
|
16497
|
+
result.failure_code = "validation_unavailable"
|
|
16498
|
+
result.error = "; ".join(critical_issues[:3])
|
|
16499
|
+
elif audit_incomplete:
|
|
16500
|
+
result.failure_code = "implementation_audit_incomplete"
|
|
16501
|
+
result.error = "; ".join(remaining[:3])
|
|
16427
16502
|
else:
|
|
16428
16503
|
result.failure_code = "validation_retry_exhausted"
|
|
16429
16504
|
result.error = (
|
|
@@ -16437,7 +16512,7 @@ class RuntimeDaemon:
|
|
|
16437
16512
|
"(proceeding anyway)",
|
|
16438
16513
|
len(remaining), max_retries, task.task_id,
|
|
16439
16514
|
)
|
|
16440
|
-
result
|
|
16515
|
+
_record_output_validation_issues(result, remaining)
|
|
16441
16516
|
self._record_testing_telemetry(workspace_path, task, result, retries_used=max_retries)
|
|
16442
16517
|
return result
|
|
16443
16518
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: forgexa-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.84.0
|
|
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
|
|
@@ -37,9 +37,9 @@ Requires-Dist: socksio>=1.0.0; extra == "daemon"
|
|
|
37
37
|
Command-line client and agent runtime for the [Forgexa](https://forgexa.net) platform.
|
|
38
38
|
|
|
39
39
|
Communicates with the Forgexa server via REST API.
|
|
40
|
-
Includes a built-in **daemon** with
|
|
40
|
+
Includes a built-in **daemon** with fourteen integrations: Claude Code, Codex, GitHub Copilot, Gemini CLI, OpenCode, Kimi Code, MiMo Code, Qwen Code, CodeBuddy, Qoder CLI, TraeCode CLI, ZCode, DeepSeek Harness, and MiniMax Code. Six standard candidates are automatically routed; eight require explicit selection. MiniMax Code and Harness require owned personal Runtimes/Desktop and local build/profile acceptance. Harness retains its exact 0.2.0-rc.2 and read-only external credential requirements. See [installation and limits](../documentation/guide/agent-installation.md).
|
|
41
41
|
|
|
42
|
-
MiniMax Code is
|
|
42
|
+
MiniMax Code is included in that count, supporting reviewed 0.7.0 with 0.6.13 compatibility. Run `forgexa agent minimax-init` to create an empty private profile, authenticate there, then use `forgexa agent minimax-status` and `forgexa agent approve-live minimax --evidence PATH` after real local acceptance. Personal credentials are never copied; Agent-only approval is refused. `forgexa check --agent minimax --quick` is non-billable; normal `check` runs a potentially billable task. See [MiniMax setup, evidence and recovery](../documentation/guide/agent-installation.md#minimax-code).
|
|
43
43
|
|
|
44
44
|
## Installation
|
|
45
45
|
|
|
@@ -67,7 +67,7 @@ def test_minimax_live_approval_forwards_bound_evidence(tmp_path, monkeypatch, ca
|
|
|
67
67
|
|
|
68
68
|
def approve(binary, document):
|
|
69
69
|
captured.append((binary, document))
|
|
70
|
-
return {"fingerprint": "bound-build", "version": "0.
|
|
70
|
+
return {"fingerprint": "bound-build", "version": "0.7.0"}
|
|
71
71
|
|
|
72
72
|
monkeypatch.setattr(agent_core, "approve_minimax_build", approve)
|
|
73
73
|
main.cmd_agent_approve_live(SimpleNamespace(agent_id="minimax", binary="/fake/mcode", evidence=str(path)))
|
|
@@ -77,7 +77,7 @@ def test_minimax_live_approval_forwards_bound_evidence(tmp_path, monkeypatch, ca
|
|
|
77
77
|
|
|
78
78
|
def test_minimax_quick_check_reports_pending_without_live_calls(monkeypatch, capsys):
|
|
79
79
|
monkeypatch.setattr(daemon.AgentDiscovery, "AGENT_REGISTRY", {"minimax": {"commands": ["mcode"], "detect": "mcode --version"}})
|
|
80
|
-
agent = _agent("minimax", "0.
|
|
80
|
+
agent = _agent("minimax", "0.7.0")
|
|
81
81
|
agent.min_version_ok = False
|
|
82
82
|
agent.version_warning = "MiniMax build/profile acceptance missing"
|
|
83
83
|
monkeypatch.setattr(daemon.AgentDiscovery, "discover", AsyncMock(return_value=[agent]))
|
|
@@ -97,7 +97,7 @@ async def test_minimax_live_check_discloses_profile_provider_and_budget(tmp_path
|
|
|
97
97
|
"root": tmp_path / "profile", "policy": {"provider": "dedicated", "region": "cn"},
|
|
98
98
|
})
|
|
99
99
|
manager = SimpleNamespace(run_agent=AsyncMock(return_value=SimpleNamespace(status="success")))
|
|
100
|
-
assert await main._live_check_agent(manager, _agent("minimax", "0.
|
|
100
|
+
assert await main._live_check_agent(manager, _agent("minimax", "0.7.0"), 15) == (True, "")
|
|
101
101
|
output = capsys.readouterr().err
|
|
102
102
|
assert "profile=profile" in output and "provider=dedicated" in output
|
|
103
103
|
assert "timeout=15s" in output and "max_steps=60" in output and "charges" in output
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|