forgexa-cli 1.8.8__tar.gz → 1.8.10__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.8.8 → forgexa_cli-1.8.10}/PKG-INFO +1 -1
- {forgexa_cli-1.8.8 → forgexa_cli-1.8.10}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.8.8 → forgexa_cli-1.8.10}/forgexa_cli/daemon.py +83 -8
- {forgexa_cli-1.8.8 → forgexa_cli-1.8.10}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.8.8 → forgexa_cli-1.8.10}/pyproject.toml +1 -1
- {forgexa_cli-1.8.8 → forgexa_cli-1.8.10}/README.md +0 -0
- {forgexa_cli-1.8.8 → forgexa_cli-1.8.10}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.8.8 → forgexa_cli-1.8.10}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.8.8 → forgexa_cli-1.8.10}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.8.8 → forgexa_cli-1.8.10}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.8.8 → forgexa_cli-1.8.10}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.8.8 → forgexa_cli-1.8.10}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.8.8 → forgexa_cli-1.8.10}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.8.8 → forgexa_cli-1.8.10}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.8.8 → forgexa_cli-1.8.10}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.8.
|
|
2
|
+
__version__ = "1.8.10"
|
|
@@ -352,7 +352,11 @@ except (ImportError, ModuleNotFoundError):
|
|
|
352
352
|
|
|
353
353
|
@property
|
|
354
354
|
def AGENT_TIMEOUT(self) -> int:
|
|
355
|
-
return int(os.environ.get("AGENT_TIMEOUT", "
|
|
355
|
+
return int(os.environ.get("AGENT_TIMEOUT", "14400")) # 4-hour absolute ceiling
|
|
356
|
+
|
|
357
|
+
@property
|
|
358
|
+
def AGENT_IDLE_TIMEOUT(self) -> int:
|
|
359
|
+
return int(os.environ.get("AGENT_IDLE_TIMEOUT", "600")) # 10-min idle (stdout+fs) = hung agent
|
|
356
360
|
|
|
357
361
|
@property
|
|
358
362
|
def GIT_CLONE_TIMEOUT(self) -> int:
|
|
@@ -392,7 +396,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
392
396
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
393
397
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
394
398
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
395
|
-
DAEMON_VERSION = "1.8.
|
|
399
|
+
DAEMON_VERSION = "1.8.10"
|
|
396
400
|
|
|
397
401
|
|
|
398
402
|
def _detect_client_type() -> str:
|
|
@@ -633,6 +637,11 @@ class TaskResult:
|
|
|
633
637
|
lines_added: int = 0
|
|
634
638
|
lines_removed: int = 0
|
|
635
639
|
error: str = ""
|
|
640
|
+
# failure_code is forwarded to the server to drive retry policy.
|
|
641
|
+
# Key values:
|
|
642
|
+
# "all_agents_rate_limited" — daemon tried every installed agent, all
|
|
643
|
+
# hit rate/quota limits. Server must NOT retry on the same runtime.
|
|
644
|
+
failure_code: str = ""
|
|
636
645
|
artifacts: list[dict] = field(default_factory=list)
|
|
637
646
|
observations: list[dict] = field(default_factory=list)
|
|
638
647
|
metrics: dict = field(default_factory=dict)
|
|
@@ -1815,6 +1824,13 @@ class WorkspaceManager:
|
|
|
1815
1824
|
if git_prefix_args:
|
|
1816
1825
|
env = {**(env or os.environ), "GIT_TERMINAL_PROMPT": "0"}
|
|
1817
1826
|
|
|
1827
|
+
# Always enable long-path support. On Windows this removes git's own
|
|
1828
|
+
# 260-char path limit (Windows also needs HKLM LongPathsEnabled=1 or
|
|
1829
|
+
# the Win10 1607+ Group Policy, but at a minimum we ensure git won't
|
|
1830
|
+
# reject long paths on platforms where it is already enabled).
|
|
1831
|
+
# On Linux/macOS this is a no-op.
|
|
1832
|
+
longpath_args = ["-c", "core.longpaths=true"]
|
|
1833
|
+
|
|
1818
1834
|
# start_new_session=True puts git in its own process group.
|
|
1819
1835
|
# On timeout we send SIGKILL to the entire group, which includes
|
|
1820
1836
|
# any ssh/gpg/credential-helper children that git forked — preventing
|
|
@@ -1822,7 +1838,7 @@ class WorkspaceManager:
|
|
|
1822
1838
|
# Windows note: start_new_session creates a new console process group;
|
|
1823
1839
|
# we use taskkill /T there instead of killpg.
|
|
1824
1840
|
proc = await asyncio.create_subprocess_exec(
|
|
1825
|
-
"git", *git_prefix_args, *args,
|
|
1841
|
+
"git", *longpath_args, *git_prefix_args, *args,
|
|
1826
1842
|
stdout=asyncio.subprocess.PIPE,
|
|
1827
1843
|
stderr=asyncio.subprocess.PIPE,
|
|
1828
1844
|
cwd=str(cwd) if cwd else None,
|
|
@@ -3503,6 +3519,7 @@ class ProgressReporter:
|
|
|
3503
3519
|
"stdout_tail": result.stdout[-20000:] if result.stdout else "",
|
|
3504
3520
|
"stderr_tail": result.stderr[-5000:] if result.stderr else "",
|
|
3505
3521
|
"error": result.error,
|
|
3522
|
+
"failure_code": result.failure_code,
|
|
3506
3523
|
"files_changed": result.files_changed,
|
|
3507
3524
|
"lines_added": result.lines_added,
|
|
3508
3525
|
"lines_removed": result.lines_removed,
|
|
@@ -4402,7 +4419,52 @@ class RuntimeDaemon:
|
|
|
4402
4419
|
)
|
|
4403
4420
|
logger.info("Workspace ready: %s", workspace_path)
|
|
4404
4421
|
|
|
4405
|
-
# 2.
|
|
4422
|
+
# 2.1 Workspace health check: detect broken checkout (Windows filename-
|
|
4423
|
+
# too-long or other git checkout failure that leaves the working tree
|
|
4424
|
+
# empty while the git index still tracks all source files).
|
|
4425
|
+
# If this is not caught the agent will run `git add -A` and commit a
|
|
4426
|
+
# catastrophic mass-deletion (e.g. SI-434: 47,566 files deleted).
|
|
4427
|
+
try:
|
|
4428
|
+
_index_count_out = await self._git(
|
|
4429
|
+
"ls-files", "--cached", "--", ".", cwd=workspace_path,
|
|
4430
|
+
timeout=30,
|
|
4431
|
+
)
|
|
4432
|
+
_index_count = len([l for l in _index_count_out.splitlines() if l.strip()])
|
|
4433
|
+
if _index_count > 500:
|
|
4434
|
+
# Count physical files (exclude .git/)
|
|
4435
|
+
_phys_count = sum(1 for _ in workspace_path.rglob("*")
|
|
4436
|
+
if _.is_file() and ".git" not in _.parts)
|
|
4437
|
+
_ratio = _phys_count / _index_count
|
|
4438
|
+
if _ratio < 0.20:
|
|
4439
|
+
# Less than 20 % of tracked files exist on disk — almost
|
|
4440
|
+
# certainly a failed git checkout (e.g. Windows path-length
|
|
4441
|
+
# limit). Abort rather than letting the agent commit a
|
|
4442
|
+
# mass-deletion.
|
|
4443
|
+
_longpath_hint = (
|
|
4444
|
+
" Enable Windows long-path support: run "
|
|
4445
|
+
"`git config --global core.longpaths true` and enable "
|
|
4446
|
+
"LongPathsEnabled in Windows Group Policy / Registry "
|
|
4447
|
+
"(HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled=1)."
|
|
4448
|
+
if sys.platform == "win32" else ""
|
|
4449
|
+
)
|
|
4450
|
+
raise RuntimeError(
|
|
4451
|
+
f"Workspace health check failed: only {_phys_count}/{_index_count} "
|
|
4452
|
+
f"tracked files exist on disk ({_ratio:.0%}). "
|
|
4453
|
+
f"The git checkout likely failed due to filename-length limitations."
|
|
4454
|
+
f"{_longpath_hint}"
|
|
4455
|
+
)
|
|
4456
|
+
elif _ratio < 0.80:
|
|
4457
|
+
logger.warning(
|
|
4458
|
+
"Workspace health check warning: only %d/%d tracked files "
|
|
4459
|
+
"exist on disk (%.0f%%) for task %s — checkout may be incomplete.",
|
|
4460
|
+
_phys_count, _index_count, _ratio * 100, task.task_id,
|
|
4461
|
+
)
|
|
4462
|
+
except RuntimeError:
|
|
4463
|
+
raise
|
|
4464
|
+
except Exception as _health_exc:
|
|
4465
|
+
logger.warning("Workspace health check error (non-fatal): %s", _health_exc)
|
|
4466
|
+
|
|
4467
|
+
|
|
4406
4468
|
# agent run starts from a completely clean slate. This covers:
|
|
4407
4469
|
# • Type change: removes old-type files (e.g. PRD.md/SDD.md) so they
|
|
4408
4470
|
# don't coexist with the new type's files (e.g. diagnosis.md).
|
|
@@ -4630,6 +4692,10 @@ class RuntimeDaemon:
|
|
|
4630
4692
|
f"Original error: {result.error}"
|
|
4631
4693
|
)
|
|
4632
4694
|
result.status = "failed"
|
|
4695
|
+
# Signal to the server that ALL installed agents were tried and
|
|
4696
|
+
# all are rate/quota limited. The server must NOT re-enqueue on
|
|
4697
|
+
# the same runtime — that would hit the same quota wall.
|
|
4698
|
+
result.failure_code = "all_agents_rate_limited"
|
|
4633
4699
|
|
|
4634
4700
|
# 4. Collect git info BEFORE commit (shows uncommitted changes)
|
|
4635
4701
|
pre_commit_git = await self.process_manager._collect_git_info(workspace_path)
|
|
@@ -5315,7 +5381,14 @@ class RuntimeDaemon:
|
|
|
5315
5381
|
],
|
|
5316
5382
|
)
|
|
5317
5383
|
|
|
5318
|
-
#
|
|
5384
|
+
# Save the original prompt BEFORE building the retry variant so we
|
|
5385
|
+
# can include it in fix_prompt. Without this the agent receives only
|
|
5386
|
+
# "fix validation errors" with zero task context and responds with
|
|
5387
|
+
# "I don't have a specific task to execute yet." (root cause confirmed
|
|
5388
|
+
# via Copilot JSONL output for SI-434/SI-446).
|
|
5389
|
+
original_prompt = task.input_prompt
|
|
5390
|
+
|
|
5391
|
+
# Build a targeted fix prompt: original task + validation issues.
|
|
5319
5392
|
_input = task.input_data or {}
|
|
5320
5393
|
_fix_doc_dir = (
|
|
5321
5394
|
_input.get("output_dir")
|
|
@@ -5323,8 +5396,11 @@ class RuntimeDaemon:
|
|
|
5323
5396
|
or ""
|
|
5324
5397
|
)
|
|
5325
5398
|
fix_prompt = (
|
|
5326
|
-
"
|
|
5327
|
-
"
|
|
5399
|
+
f"{original_prompt}\n\n"
|
|
5400
|
+
"---\n\n"
|
|
5401
|
+
"**IMPORTANT – Validation Retry:** The previous execution attempt "
|
|
5402
|
+
"did not produce all required output. Please complete the task above "
|
|
5403
|
+
"and ensure ALL of the following issues are resolved:\n\n"
|
|
5328
5404
|
f"{issues_text}\n\n"
|
|
5329
5405
|
)
|
|
5330
5406
|
if _fix_doc_dir:
|
|
@@ -5339,7 +5415,6 @@ class RuntimeDaemon:
|
|
|
5339
5415
|
)
|
|
5340
5416
|
|
|
5341
5417
|
# Override task prompt temporarily
|
|
5342
|
-
original_prompt = task.input_prompt
|
|
5343
5418
|
task.input_prompt = fix_prompt
|
|
5344
5419
|
|
|
5345
5420
|
try:
|
|
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
|