vibego 1.0.2__py3-none-any.whl → 1.0.5__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.

Potentially problematic release.


This version of vibego might be problematic. Click here for more details.

master.py CHANGED
@@ -2198,7 +2198,8 @@ async def _process_restart_request(
2198
2198
 
2199
2199
  lock = _ensure_restart_lock()
2200
2200
  async with lock:
2201
- global_restart_in_progress
2201
+ # Use global so the restart flag is shared across concurrent handlers
2202
+ global _restart_in_progress
2202
2203
  if _restart_in_progress:
2203
2204
  await message.answer("A restart request is already being executed, please try again later. ")
2204
2205
  return
@@ -2258,7 +2259,8 @@ async def cmd_start(message: Message) -> None:
2258
2259
 
2259
2260
  async def _perform_restart(message: Message, start_script: Path) -> None:
2260
2261
  """Asynchronous execution ./start.sh,If it fails, roll back the mark and notify the administrator """
2261
- global_restart_in_progress
2262
+ # Use global so restart flag resets affect the module-level state
2263
+ global _restart_in_progress
2262
2264
  lock = _ensure_restart_lock()
2263
2265
  bot = message.bot
2264
2266
  chat_id = message.chat.id
@@ -1,137 +1,65 @@
1
1
  #!/usr/bin/env python3
2
- """Master Post-startup health check script.
2
+ """Master post-startup health check.
3
3
 
4
- Process:
5
- 1. Wait for a startup flag to appear in the master log.
6
- 2. Call MasterManager to start the specified worker (default hyphavibebotbackend).
7
- 3. Automatically discover the worker's chat_id(Prioritize the state file, then read the latest log).
8
- 4. Send a probe message to the chat through the Telegram Bot API to confirm that the sending is successful.
9
- 5. If any step fails, an exception is thrown and an attempt is made to notify the administrator.
10
-
11
- Note: This script will not automatically retry the restart and will only return a non-zero exit code for processing by the outer script.
4
+ This lightweight variant only verifies that the master process has emitted the
5
+ readiness marker in its log file. Worker bootstrap and Telegram probes are no
6
+ longer executed because master restart confirmation is now limited to the
7
+ controller process itself.
12
8
  """
13
9
  from __future__ import annotations
14
10
 
15
11
  import argparse
16
- import asyncio
17
12
  import json
18
13
  import os
19
- import re
20
14
  import sys
21
15
  import time
22
16
  from pathlib import Path
23
- from typing import Optional
24
- from urllib.error import URLError, HTTPError
25
17
  from urllib.request import Request, urlopen
26
18
 
27
- # Import the configuration and tools in master and reuse the project parsing logic
19
+ # Make sure the master module can be imported from the repository root
28
20
  ROOT_DIR = Path(__file__).resolve().parent.parent
29
21
  ROOT_DIR_STR = str(ROOT_DIR)
30
22
  if ROOT_DIR_STR not in sys.path:
31
- # Make sure the master module can be imported from the repository root
32
23
  sys.path.insert(0, ROOT_DIR_STR)
33
24
 
34
25
  import master # type: ignore
35
- from project_repository import ProjectRepository
26
+
36
27
  DEFAULT_MASTER_LOG = master.LOG_ROOT_PATH / "vibe.log"
37
28
  DEFAULT_TIMEOUT_MASTER = 60.0
38
- DEFAULT_TIMEOUT_PROBE = 15.0
39
- PROBE_TEXT = "hello"
40
- REPOSITORY = ProjectRepository(master.CONFIG_DB_PATH, master.CONFIG_PATH)
41
-
42
-
43
- def _load_project(project_id: str) -> master.ProjectConfig:
44
- """Get the project configuration based on the slug or bot name, and list the options on failure."""
45
-
46
- record = REPOSITORY.get_by_slug(project_id)
47
- if record is None:
48
- record = REPOSITORY.get_by_bot_name(project_id)
49
- if record is None:
50
- available = [r.project_slug for r in REPOSITORY.list_projects()]
51
- raise RuntimeError(f"No items found {project_id}, Optional items: {available}")
52
- return master.ProjectConfig.from_dict(record.to_dict())
29
+ MASTER_READY_MARKER = "Master Started, listening for administrator commands."
53
30
 
54
31
 
55
32
  def _wait_for_log_flag(path: Path, pattern: str, timeout: float) -> None:
56
- """Waits for a specific mark in the log within the timeout period."""
33
+ """Poll the master log until the readiness marker is detected or timeout."""
57
34
 
58
35
  deadline = time.monotonic() + timeout
59
- position = 0
36
+ if path.exists():
37
+ position = path.stat().st_size
38
+ initialized = True
39
+ else:
40
+ position = 0
41
+ initialized = False
60
42
  while time.monotonic() < deadline:
61
43
  if path.exists():
62
- if position == 0:
63
- position = path.stat().st_size
44
+ if not initialized:
45
+ position = 0
46
+ initialized = True
64
47
  with path.open("r", encoding="utf-8", errors="ignore") as fh:
65
48
  fh.seek(position)
66
49
  while time.monotonic() < deadline:
67
50
  line = fh.readline()
68
51
  if not line:
69
52
  time.sleep(0.5)
70
- continue
53
+ break
71
54
  position = fh.tell()
72
55
  if pattern in line:
73
56
  return
74
57
  time.sleep(0.5)
75
- raise TimeoutError(f"exist {timeout:.0f} No log markers detected in seconds: {pattern}")
76
-
77
-
78
- def _extract_chat_id_from_logs(log_path: Path) -> Optional[int]:
79
- """Find the most recent chat from the log file in reverse order_id."""
80
-
81
- if not log_path.exists():
82
- return None
83
- pattern = re.compile(r"chat=(-?\d+)")
84
- try:
85
- lines = log_path.read_text(encoding="utf-8", errors="ignore").splitlines()
86
- except Exception:
87
- return None
88
- for line in reversed(lines[-200:]): # Reverse search for recent records
89
- match = pattern.search(line)
90
- if match:
91
- try:
92
- return int(match.group(1))
93
- except ValueError:
94
- continue
95
- return None
96
-
97
-
98
- def _ensure_chat_id(cfg: master.ProjectConfig, manager: master.MasterManager) -> int:
99
- """Make sure the task is assigned chat_id, Backfill from log and write back to state if necessary."""
100
-
101
- state = manager.state_store.data.get(cfg.project_slug)
102
- if state and state.chat_id:
103
- return int(state.chat_id)
104
- # Fall back to log search
105
- log_dir = master.LOG_ROOT_PATH / (cfg.default_model.lower()) / cfg.project_slug
106
- chat_id = _extract_chat_id_from_logs(log_dir / "run_bot.log")
107
- if chat_id is None:
108
- raise RuntimeError(
109
- "Unable to get chat automatically_id, Please manually have a conversation with the bot to write the state/log"
110
- )
111
- # will discover chat_id Write back the state for easy reuse next time
112
- manager.state_store.update(cfg.project_slug, chat_id=chat_id)
113
- return chat_id
114
-
115
-
116
- def _send_probe(bot_token: str, chat_id: int, text: str, timeout: float) -> None:
117
- """Send a probe message to the specified chat to verify that the Telegram API is available."""
118
-
119
- url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
120
- payload = json.dumps({"chat_id": chat_id, "text": text, "disable_notification": True}).encode("utf-8")
121
- request = Request(url, data=payload, headers={"Content-Type": "application/json"}, method="POST")
122
- try:
123
- with urlopen(request, timeout=timeout) as resp:
124
- data = json.loads(resp.read().decode("utf-8"))
125
- except HTTPError as exc: # pragma: no cover - Thrown when network exception occurs
126
- raise RuntimeError(f"Failed to send probe message, HTTP {exc.code}: {exc.reason}") from exc
127
- except URLError as exc: # pragma: no cover - Thrown when network exception occurs
128
- raise RuntimeError(f"Failed to send probe message: {exc}") from exc
129
- if not data.get("ok"):
130
- raise RuntimeError(f"Failed to send probe message: {data}")
58
+ raise TimeoutError(f"No log markers detected within {timeout:.0f}s: {pattern}")
131
59
 
132
60
 
133
61
  def _format_admin_notice(reason: str) -> str:
134
- """Generate alarm text to notify the administrator."""
62
+ """Compose the notification text for administrator alerts."""
135
63
 
136
64
  return (
137
65
  "Master Restart health check failed\n"
@@ -141,7 +69,7 @@ def _format_admin_notice(reason: str) -> str:
141
69
 
142
70
 
143
71
  def _notify_admins(reason: str) -> None:
144
- """If the master token is available, the failure reason is broadcast to the administrator list."""
72
+ """Broadcast the failure reason to administrators if the master token exists."""
145
73
 
146
74
  master_token = os.environ.get("MASTER_BOT_TOKEN")
147
75
  if not master_token:
@@ -163,58 +91,30 @@ def _notify_admins(reason: str) -> None:
163
91
  continue
164
92
 
165
93
 
166
- def _ensure_worker(cfg: master.ProjectConfig) -> master.MasterManager:
167
- """Starts the specified project worker and returns the temporarily constructed MasterManager."""
168
-
169
- records = REPOSITORY.list_projects()
170
- configs = [master.ProjectConfig.from_dict(record.to_dict()) for record in records]
171
- state_store = master.StateStore(
172
- master.STATE_PATH, {item.project_slug: item for item in configs}
173
- )
174
- manager = master.MasterManager(configs, state_store=state_store)
175
-
176
- async def _run() -> None:
177
- """The coroutine performs the actual stop/start process."""
178
- # Make sure to stop the old instance first(If it existsexist)
179
- try:
180
- await manager.stop_worker(cfg)
181
- except Exception:
182
- pass
183
- await manager.run_worker(cfg)
184
-
185
- asyncio.run(_run())
186
- return manager
187
-
188
-
189
94
  def main() -> int:
190
- """Command line entry, performs master health check and returns exit code."""
191
-
192
- parser = argparse.ArgumentParser(description="Master Post-launch health check")
193
- parser.add_argument("--project", default="hyphavibebotbackend", help="Project slug or bot name")
194
- parser.add_argument("--master-log", default=str(DEFAULT_MASTER_LOG), help="master Log path")
195
- parser.add_argument("--master-timeout", type=float, default=DEFAULT_TIMEOUT_MASTER, help="master Log wait timeout (Second)")
196
- parser.add_argument("--probe-timeout", type=float, default=DEFAULT_TIMEOUT_PROBE, help="Telegram Probe timeout (Second)")
95
+ """Command line entry point, only validates master readiness."""
96
+
97
+ parser = argparse.ArgumentParser(description="Master post-launch health check (master only)")
98
+ parser.add_argument("--master-log", default=str(DEFAULT_MASTER_LOG), help="Master log path")
99
+ parser.add_argument(
100
+ "--master-timeout",
101
+ type=float,
102
+ default=DEFAULT_TIMEOUT_MASTER,
103
+ help="Master log wait timeout (seconds)",
104
+ )
197
105
  args = parser.parse_args()
198
106
 
199
- project_id = master._sanitize_slug(args.project)
200
107
  master_log = Path(args.master_log)
201
108
 
202
109
  try:
203
- _wait_for_log_flag(master_log, "Master Started, listening for administrator commands.", args.master_timeout)
204
- cfg = _load_project(project_id)
205
- manager = _ensure_worker(cfg)
206
- chat_id = _ensure_chat_id(cfg, manager)
207
- _send_probe(cfg.bot_token, chat_id, PROBE_TEXT, args.probe_timeout)
110
+ _wait_for_log_flag(master_log, MASTER_READY_MARKER, args.master_timeout)
208
111
  except Exception as exc:
209
112
  reason = str(exc)
210
113
  _notify_admins(reason)
211
114
  print(f"[healthcheck] fail: {reason}", file=sys.stderr)
212
115
  return 1
213
116
  else:
214
- print(
215
- "[healthcheck] success: master ready,"
216
- f"worker={cfg.display_name} Startup completed, chat_id={chat_id}, Probe message sent"
217
- )
117
+ print("[healthcheck] success: master ready, worker checks skipped by configuration")
218
118
  return 0
219
119
 
220
120
 
scripts/start.sh CHANGED
@@ -118,8 +118,9 @@ ensure_codex_installed() {
118
118
  ensure_codex_installed
119
119
 
120
120
  select_python_binary() {
121
- # Choose to meet CPython <=3.12 interpreter, disabled by default 3.13(pydantic-core There is no compatible wheel in the pipx base environment)
121
+ # Select a compatible CPython version; defaults accept 3.9-3.14 and can be overridden via env variables.
122
122
  local allow_py313="${VIBEGO_ALLOW_PY313:-}"
123
+ local supported_max_minor="${VIBEGO_MAX_MINOR:-14}"
123
124
  local candidates=()
124
125
  local chosen=""
125
126
  local name
@@ -152,18 +153,24 @@ select_python_binary() {
152
153
  if [[ -n "${VIBEGO_PYTHON:-}" && "$name" == "$VIBEGO_PYTHON" ]]; then
153
154
  explicit_override=1
154
155
  fi
155
- if [[ "$minor" =~ ^[0-9]+$ ]] && (( minor == 13 )) && [[ "$allow_py313" != "1" ]] && (( explicit_override == 0 )); then
156
- log_line "jump over ${name} (Version ${version_raw}): Python 3 is disabled by default.13, Configurable VIBEGO_ALLOW_PY313=1 cover" >&2
157
- continue
156
+ if [[ "$minor" =~ ^[0-9]+$ ]] && (( minor == 13 )) && (( explicit_override == 0 )); then
157
+ if [[ "$allow_py313" == "0" ]]; then
158
+ log_line "Skip ${name} (version ${version_raw}): disabled explicitly by VIBEGO_ALLOW_PY313=0" >&2
159
+ continue
160
+ fi
161
+ log_line "Detected ${name} (version ${version_raw}): Python 3.13 accepted by default; use VIBEGO_ALLOW_PY313=1 to prefer or 0 to disable" >&2
158
162
  fi
159
- if [[ "$minor" =~ ^[0-9]+$ ]] && (( minor > 13 )); then
160
- log_line "jump over ${name} (Version ${version_raw}): higher than 3.13" >&2
163
+ if [[ "$minor" =~ ^[0-9]+$ ]] && (( minor > supported_max_minor )) && (( explicit_override == 0 )); then
164
+ log_line "Skip ${name} (version ${version_raw}): above supported ceiling 3.${supported_max_minor}; override with VIBEGO_MAX_MINOR if needed" >&2
161
165
  continue
162
166
  fi
163
167
  if [[ "$minor" =~ ^[0-9]+$ ]] && (( minor < 9 )); then
164
168
  log_line "jump over ${name} (Version ${version_raw}): less than 3.9, May be missing official wheels" >&2
165
169
  continue
166
170
  fi
171
+ if [[ "$minor" =~ ^[0-9]+$ ]] && (( minor >= 14 )); then
172
+ log_line "Detected ${name} (version ${version_raw}): ensure dependencies support this Python version" >&2
173
+ fi
167
174
  chosen="$name"
168
175
  log_line "Using the Python interpreter:${chosen} (Version ${version_raw})" >&2
169
176
  break
@@ -438,13 +445,13 @@ fi
438
445
  log_info "master Started in background, PID=$MASTER_PID, Log writing ${LOG_FILE}"
439
446
 
440
447
  # Health check: wait for master to come online and verify key workers
441
- log_info "Start health check..."
448
+ log_info "Start master readiness check..."
442
449
  HEALTHCHECK_START=$(date +%s)
443
450
 
444
- if python scripts/master_healthcheck.py --project hyphavibebotbackend; then
451
+ if python scripts/master_healthcheck.py --master-log "$LOG_FILE"; then
445
452
  HEALTHCHECK_END=$(date +%s)
446
453
  HEALTHCHECK_DURATION=$((HEALTHCHECK_END - HEALTHCHECK_START))
447
- log_info "OK: Master health check passed (elapsed ${HEALTHCHECK_DURATION}s)"
454
+ log_info "OK: Master readiness confirmed (elapsed ${HEALTHCHECK_DURATION}s)"
448
455
  else
449
456
  HEALTHCHECK_END=$(date +%s)
450
457
  HEALTHCHECK_DURATION=$((HEALTHCHECK_END - HEALTHCHECK_START))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vibego
3
- Version: 1.0.2
3
+ Version: 1.0.5
4
4
  Summary: vibego CLI: tools for bootstrapping and managing the Telegram Master Bot
5
5
  Author: DavidChan
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,15 +1,15 @@
1
1
  bot.py,sha256=OQj1KgrDie-8ucUdFafOxmFw2LFEElluWW6KKRy9qng,293940
2
2
  logging_setup.py,sha256=ViXK11TNVakIbA8QMLKXa6ywpAV_j1ydcws0Hx2QqNo,5084
3
- master.py,sha256=Tpw8LR3MUM1CEitUib75ckgstaJeBwq1M_wXtajumiE,131994
3
+ master.py,sha256=7HBIVt3eTT_MDqD03nfsJZ2IkQfDpaPVL4zOTWdCKkE,132144
4
4
  project_repository.py,sha256=fdO3xzvt7eBLvek_3mXjXkFpALwllrNWDWeSjtscW2I,17675
5
5
  scripts/__init__.py,sha256=Sg-nUv9JfbojPpjpHlxImt6tA0p5-z1AqDA6csaFNZI,74
6
6
  scripts/bump_version.sh,sha256=bAV-NHTSmqnjrsHjvrNTF3wS2rxhF-lZ5h5kpK2-Zyc,4462
7
7
  scripts/log_writer.py,sha256=65HGLPREiBL1b4QRiMdxGmHfT21WBEUvg9RuCjP73Kc,3957
8
- scripts/master_healthcheck.py,sha256=QEWOCz4di_qPXxC0axtBdrsoaBXg2tpGpRObci1ITQ8,8935
8
+ scripts/master_healthcheck.py,sha256=7uCCuKeyE70SjzWbT_ILxng3OUY1As2A0AeyGSOcaUg,4009
9
9
  scripts/publish.sh,sha256=e3tzRFtweJnLpJin0i43xpCWseX4jzeO2sAd2eGcTKk,3686
10
10
  scripts/requirements.txt,sha256=ukJbFLJyzqnQYMz6j07O-IOrG87IwXg0oikmn1nfJ9M,91
11
11
  scripts/run_bot.sh,sha256=I_b9zyIU1DicVHM8XJ-knAhJnj2pEHYOYpHjeHgZth8,5188
12
- scripts/start.sh,sha256=_6O9oSj-xhouXwGtho3z2oCpQ0nw5Iai8Ln13D9xmyE,15895
12
+ scripts/start.sh,sha256=nqJHO9iJ-OHbLMaLxAyycRT0o1rprrzSVNwQV4QEphA,16350
13
13
  scripts/start_tmux_codex.sh,sha256=zyrLV2vzY8NTua9_d-QLq0cVMZVbMiFCZ-UM5Lwj-FU,4502
14
14
  scripts/stop_all.sh,sha256=bvl3--jNnXLt9xUrzPqXt8SWUNLAKTELwlMArU5-8gU,4400
15
15
  scripts/stop_bot.sh,sha256=1pI8hxQRIWJTOCLXddWMOmsnk3XIkuAlQwF9mPqRW_k,6163
@@ -426,15 +426,15 @@ tasks/constants.py,sha256=BNMxSnswF-PTij-p7paMK0G5Tj6wKN-6qpUlMwo1JQA,318
426
426
  tasks/fsm.py,sha256=6n0hdFNF3j6ZUcpdJ_TwZBgkrE8aMYHNLHppdGbXGO4,1494
427
427
  tasks/models.py,sha256=3OJL3F3nVZIQIejL6LplZkPQxYJXgOhCNA9Rikg8ihk,2461
428
428
  tasks/service.py,sha256=rzWFcwDh58Xeek0fOABXt7oFmaOC0ubunAeGeaK1_uA,42130
429
- vibego-1.0.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
430
- vibego_cli/__init__.py,sha256=lvIyyWNVgNs4uzn5hhILunwdhlqcs4ePCSvZWtP9mQA,349
429
+ vibego-1.0.5.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
430
+ vibego_cli/__init__.py,sha256=_ZU634Qo6uVGiUlIrzAnCqpBCHB5lHIiD8XjjxnV0aY,349
431
431
  vibego_cli/__main__.py,sha256=lM_m-i1x3yVNQhzRt8wqvuIYeeo1OlLNAEVoJVD7tmw,155
432
432
  vibego_cli/config.py,sha256=W-n5y4CTrRpencadWlmhpdrhUHX6tdXQEGXJG1pRM0U,3149
433
433
  vibego_cli/deps.py,sha256=nFT-DMsfAiD1lLFnDotxe6aYfeeD9V5TrIPbIbAMTF4,1478
434
434
  vibego_cli/main.py,sha256=CZAUHWr45GzzEfALoLuSrnvG2VUuCexiFuCpIvyH8Jc,12506
435
435
  vibego_cli/data/worker_requirements.txt,sha256=QSt30DSSSHtfucTFPpc7twk9kLS5rVLNTcvDiagxrZg,62
436
- vibego-1.0.2.dist-info/METADATA,sha256=FDMpU4rgKvX7HNtaOxsQv1me9dV_M9c2w88Sc9PyetU,12584
437
- vibego-1.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
438
- vibego-1.0.2.dist-info/entry_points.txt,sha256=Lsy_zm-dlyxt8-9DL9blBReIwU2k22c8-kifr46ND1M,48
439
- vibego-1.0.2.dist-info/top_level.txt,sha256=R56CT3nW5H5v3ce0l3QDN4-C4qxTrNWzRTwrxnkDX4U,69
440
- vibego-1.0.2.dist-info/RECORD,,
436
+ vibego-1.0.5.dist-info/METADATA,sha256=C2ZQFURSlr3I99EstFokPG8quQTlKkQ7SucqugcZRs8,12584
437
+ vibego-1.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
438
+ vibego-1.0.5.dist-info/entry_points.txt,sha256=Lsy_zm-dlyxt8-9DL9blBReIwU2k22c8-kifr46ND1M,48
439
+ vibego-1.0.5.dist-info/top_level.txt,sha256=R56CT3nW5H5v3ce0l3QDN4-C4qxTrNWzRTwrxnkDX4U,69
440
+ vibego-1.0.5.dist-info/RECORD,,
vibego_cli/__init__.py CHANGED
@@ -8,6 +8,6 @@ from __future__ import annotations
8
8
 
9
9
  __all__ = ["main", "__version__"]
10
10
 
11
- __version__ = "1.0.2"
11
+ __version__ = "1.0.5"
12
12
 
13
13
  from .main import main # noqa: E402
File without changes