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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.8.8
3
+ Version: 1.8.10
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: MIT
@@ -1,2 +1,2 @@
1
1
  """forgexa-cli — Forgexa command-line client."""
2
- __version__ = "1.8.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", "3600"))
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.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.5 Wipe the analysis output directory on fresh analysis so the new
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
- # Build a targeted fix prompt with output directory context
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
- "The previous execution produced output with validation errors.\n"
5327
- "Please fix ALL of the following issues:\n\n"
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.8.8
3
+ Version: 1.8.10
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: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "forgexa-cli"
3
- version = "1.8.8"
3
+ version = "1.8.10"
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 = { text = "MIT" }
File without changes
File without changes