henosis-cli 0.6.6__py3-none-any.whl → 0.6.7__py3-none-any.whl

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: henosis-cli
3
- Version: 0.6.6
3
+ Version: 0.6.7
4
4
  Summary: henosis-cli — interactive CLI for the Henosis multi-provider streaming chat backend, with optional local tools.
5
5
  Author-email: henosis <henosis@henosis.us>
6
6
  License-Expression: LicenseRef-Proprietary
@@ -3,9 +3,9 @@ henosis_cli_tools/__init__.py,sha256=x3uaN_ub32uALx_oURna0VnuoSsj7i9NYY6uRsc2ZzM
3
3
  henosis_cli_tools/cli_entry.py,sha256=OZTe_s9Hfy3mcsYG77T3RTdtCDod-CSwmhskbXjmmqs,1713
4
4
  henosis_cli_tools/input_engine.py,sha256=kGW6AgDGbdcVxlx5mvTPKYe4lYhho5wztvUAw7WlmTs,15286
5
5
  henosis_cli_tools/settings_ui.py,sha256=8rWsp0S3wT-dgkP0y20FOBmBBy7jYbDy8AuftmKcp4w,21368
6
- henosis_cli_tools/tool_impl.py,sha256=oZGajJMkR2jfyuPVe-iq2s6ktOl5-2K3RzX2R7FIAFQ,37414
7
- henosis_cli-0.6.6.dist-info/METADATA,sha256=FTcN41P41geCQJDQ3l8gechD9F7V1SwKIzkUB_P98Rg,5787
8
- henosis_cli-0.6.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
- henosis_cli-0.6.6.dist-info/entry_points.txt,sha256=KmXDdmIjq1SVMs8FK3wHPA2i89RMaerzZHIetllMLIk,74
10
- henosis_cli-0.6.6.dist-info/top_level.txt,sha256=u7XMBcJ8Kb0n91WaSU-4Db8yURSUXFuOxGMsXti0a-g,34
11
- henosis_cli-0.6.6.dist-info/RECORD,,
6
+ henosis_cli_tools/tool_impl.py,sha256=0iojZbVZhhPJybcmb2qYAuCesgQMp83JgPL2Py4PjT8,39250
7
+ henosis_cli-0.6.7.dist-info/METADATA,sha256=orIj5bXfMXfOSh3TeKpGkgOUWv6Ea2pBKB5rzkAbcKk,5787
8
+ henosis_cli-0.6.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ henosis_cli-0.6.7.dist-info/entry_points.txt,sha256=KmXDdmIjq1SVMs8FK3wHPA2i89RMaerzZHIetllMLIk,74
10
+ henosis_cli-0.6.7.dist-info/top_level.txt,sha256=u7XMBcJ8Kb0n91WaSU-4Db8yURSUXFuOxGMsXti0a-g,34
11
+ henosis_cli-0.6.7.dist-info/RECORD,,
@@ -70,12 +70,37 @@ _DEFAULT_ALLOWED_EXTS = {
70
70
  ".conf",
71
71
  }
72
72
 
73
- _MAX_FILE_BYTES = int(os.getenv("HENOSIS_MAX_FILE_BYTES", str(1_073_741_824)))
74
- _MAX_EDIT_BYTES = int(os.getenv("HENOSIS_MAX_EDIT_BYTES", str(1_073_741_824)))
75
- _EDIT_SAFEGUARD_MAX_LINES = int(os.getenv("HENOSIS_EDIT_SAFEGUARD_MAX_LINES", "3000"))
76
- _COMMAND_TIMEOUT_SEC = float(os.getenv("HENOSIS_COMMAND_TIMEOUT_SEC", "30"))
77
- # Max chars for stdout/stderr before truncation notice is applied
78
- _CMD_OUTPUT_MAX_CHARS = 3000
73
+ _MAX_FILE_BYTES = int(os.getenv("HENOSIS_MAX_FILE_BYTES", str(1_073_741_824)))
74
+ _MAX_EDIT_BYTES = int(os.getenv("HENOSIS_MAX_EDIT_BYTES", str(1_073_741_824)))
75
+ _EDIT_SAFEGUARD_MAX_LINES = int(os.getenv("HENOSIS_EDIT_SAFEGUARD_MAX_LINES", "3000"))
76
+
77
+ # Command timeout behavior:
78
+ # - The tool call can request a per-invocation timeout via the `timeout` argument.
79
+ # - The client/user may configure a DEFAULT (used when the tool omits timeout)
80
+ # and a MAX (hard cap for safety).
81
+ # - Backward compatibility: legacy env var HENOSIS_COMMAND_TIMEOUT_SEC is treated as MAX.
82
+ def _env_float(name: str, default: float) -> float:
83
+ try:
84
+ v = os.getenv(name, "")
85
+ if v is None:
86
+ return float(default)
87
+ s = str(v).strip()
88
+ if not s:
89
+ return float(default)
90
+ return float(s)
91
+ except Exception:
92
+ return float(default)
93
+
94
+
95
+ _COMMAND_TIMEOUT_DEFAULT_SEC = _env_float("HENOSIS_COMMAND_TIMEOUT_DEFAULT_SEC", 360.0)
96
+ _LEGACY_COMMAND_TIMEOUT_SEC_RAW = os.getenv("HENOSIS_COMMAND_TIMEOUT_SEC", "")
97
+ if str(_LEGACY_COMMAND_TIMEOUT_SEC_RAW or "").strip():
98
+ _COMMAND_TIMEOUT_MAX_SEC = _env_float("HENOSIS_COMMAND_TIMEOUT_SEC", 900.0)
99
+ else:
100
+ _COMMAND_TIMEOUT_MAX_SEC = _env_float("HENOSIS_COMMAND_TIMEOUT_MAX_SEC", 900.0)
101
+
102
+ # Max chars for stdout/stderr before truncation notice is applied
103
+ _CMD_OUTPUT_MAX_CHARS = 3000
79
104
 
80
105
  _VERBOSE_NOTICE = (
81
106
  "console output was EXTEREMELY verbose the ouput was truncated as to not overflow your context. here are the last 3k chars:\n"
@@ -322,13 +347,22 @@ def run_command(cmd: str, policy: FileToolPolicy, cwd: Optional[str] = None, tim
322
347
  base = os.path.basename(exe).lower()
323
348
  if (not allow_all) and (base not in allow_set):
324
349
  return {"ok": False, "error": f"command '{base}' not allowed"}
325
- try:
326
- requested = float(timeout) if timeout is not None else _COMMAND_TIMEOUT_SEC
327
- except Exception:
328
- requested = _COMMAND_TIMEOUT_SEC
329
- timeout_s = min(max(0.01, requested), float(_COMMAND_TIMEOUT_SEC))
330
- start = time.time()
331
- try:
350
+ # Determine effective timeout: tool-controlled within a user-configurable maximum.
351
+ try:
352
+ requested = float(timeout) if timeout is not None else float(_COMMAND_TIMEOUT_DEFAULT_SEC)
353
+ except Exception:
354
+ requested = float(_COMMAND_TIMEOUT_DEFAULT_SEC)
355
+ try:
356
+ max_sec = float(_COMMAND_TIMEOUT_MAX_SEC)
357
+ except Exception:
358
+ max_sec = 900.0
359
+ if max_sec <= 0:
360
+ # Degenerate config; keep tool safe.
361
+ max_sec = 0.01
362
+ timeout_s = min(max(0.01, requested), max_sec)
363
+ timeout_was_clamped = bool(requested > max_sec)
364
+ start = time.time()
365
+ try:
332
366
  # Force UTF-8 decoding with replacement to avoid locale-dependent decode errors
333
367
  # on Windows (e.g., cp1252 UnicodeDecodeError in reader thread).
334
368
  proc = subprocess.run(
@@ -341,39 +375,51 @@ def run_command(cmd: str, policy: FileToolPolicy, cwd: Optional[str] = None, tim
341
375
  errors="replace",
342
376
  timeout=timeout_s,
343
377
  )
344
- dur_ms = int((time.time() - start) * 1000)
378
+ dur_ms = int((time.time() - start) * 1000)
345
379
  # Truncate very verbose outputs to protect context size
346
380
  out = _truncate_if_verbose(proc.stdout)
347
381
  err = _truncate_if_verbose(proc.stderr)
348
- return {
349
- "ok": True,
350
- "data": {
351
- "cmd": cmd_str,
352
- "cwd": str(cwd_path),
353
- "exit_code": proc.returncode,
354
- "stdout": out,
355
- "stderr": err,
356
- "timed_out": False,
357
- "duration_ms": dur_ms,
358
- },
359
- }
360
- except subprocess.TimeoutExpired as e:
361
- dur_ms = int((time.time() - start) * 1000)
382
+ return {
383
+ "ok": True,
384
+ "data": {
385
+ "cmd": cmd_str,
386
+ "cwd": str(cwd_path),
387
+ "exit_code": proc.returncode,
388
+ "stdout": out,
389
+ "stderr": err,
390
+ "timed_out": False,
391
+ "duration_ms": dur_ms,
392
+ "timeout_requested_sec": requested,
393
+ "timeout_effective_sec": timeout_s,
394
+ "timeout_max_sec": max_sec,
395
+ "timeout_was_clamped": timeout_was_clamped,
396
+ },
397
+ }
398
+ except subprocess.TimeoutExpired as e:
399
+ dur_ms = int((time.time() - start) * 1000)
362
400
  # Even in timeout, ensure any captured output is truncated if overly verbose
363
401
  out = _truncate_if_verbose(e.stdout or "")
364
402
  err = _truncate_if_verbose(e.stderr or "")
365
- return {
366
- "ok": True,
367
- "data": {
368
- "cmd": cmd_str,
369
- "cwd": str(cwd_path),
370
- "exit_code": None,
371
- "stdout": out,
372
- "stderr": err,
373
- "timed_out": True,
374
- "duration_ms": dur_ms,
375
- },
376
- }
403
+ return {
404
+ "ok": True,
405
+ "data": {
406
+ "cmd": cmd_str,
407
+ "cwd": str(cwd_path),
408
+ "exit_code": None,
409
+ "stdout": out,
410
+ "stderr": err,
411
+ "timed_out": True,
412
+ "duration_ms": dur_ms,
413
+ "timeout_requested_sec": requested,
414
+ "timeout_effective_sec": timeout_s,
415
+ "timeout_max_sec": max_sec,
416
+ "timeout_was_clamped": timeout_was_clamped,
417
+ "message": (
418
+ f"Command exceeded timeout (effective_timeout={timeout_s}s). "
419
+ "Process was terminated."
420
+ ),
421
+ },
422
+ }
377
423
 
378
424
  # ---------------------------- apply_patch ----------------------------#
379
425
  def _ap_normalize_unicode(s: str) -> str: