vibego 0.2.11__py3-none-any.whl → 0.2.13__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.
- bot.py +347 -35
- scripts/bump_version.sh +152 -0
- scripts/models/claudecode.sh +4 -0
- scripts/run_bot.sh +3 -0
- scripts/start_tmux_codex.sh +8 -1
- scripts/stop_bot.sh +67 -3
- {vibego-0.2.11.dist-info → vibego-0.2.13.dist-info}/METADATA +1 -1
- {vibego-0.2.11.dist-info → vibego-0.2.13.dist-info}/RECORD +12 -11
- vibego_cli/__init__.py +1 -1
- {vibego-0.2.11.dist-info → vibego-0.2.13.dist-info}/WHEEL +0 -0
- {vibego-0.2.11.dist-info → vibego-0.2.13.dist-info}/entry_points.txt +0 -0
- {vibego-0.2.11.dist-info → vibego-0.2.13.dist-info}/top_level.txt +0 -0
bot.py
CHANGED
|
@@ -219,7 +219,7 @@ SEND_RETRY_BASE_DELAY = float(os.environ.get("SEND_RETRY_BASE_DELAY", "0.5"))
|
|
|
219
219
|
SEND_FAILURE_NOTICE_COOLDOWN = float(os.environ.get("SEND_FAILURE_NOTICE_COOLDOWN", "30"))
|
|
220
220
|
SESSION_INITIAL_BACKTRACK_BYTES = int(os.environ.get("SESSION_INITIAL_BACKTRACK_BYTES", "16384"))
|
|
221
221
|
ENABLE_PLAN_PROGRESS = (os.environ.get("ENABLE_PLAN_PROGRESS", "1").strip().lower() not in {"0", "false", "no", "off"})
|
|
222
|
-
AUTO_COMPACT_THRESHOLD = max(_env_int("AUTO_COMPACT_THRESHOLD",
|
|
222
|
+
AUTO_COMPACT_THRESHOLD = max(_env_int("AUTO_COMPACT_THRESHOLD", 0), 0)
|
|
223
223
|
|
|
224
224
|
PLAN_STATUS_LABELS = {
|
|
225
225
|
"completed": "✅",
|
|
@@ -967,6 +967,9 @@ async def _dispatch_prompt_to_model(
|
|
|
967
967
|
return True, session_path
|
|
968
968
|
await asyncio.sleep(0.3)
|
|
969
969
|
|
|
970
|
+
# 中断旧的延迟轮询(如果存在)
|
|
971
|
+
await _interrupt_long_poll(chat_id)
|
|
972
|
+
|
|
970
973
|
watcher_task = asyncio.create_task(
|
|
971
974
|
_watch_and_notify(
|
|
972
975
|
chat_id,
|
|
@@ -1200,6 +1203,118 @@ def _resolve_worker_target_chat_ids() -> List[int]:
|
|
|
1200
1203
|
return sorted(targets)
|
|
1201
1204
|
|
|
1202
1205
|
|
|
1206
|
+
def _auto_record_chat_id(chat_id: int) -> None:
|
|
1207
|
+
"""首次收到消息时自动将 chat_id 记录到 state 文件。
|
|
1208
|
+
|
|
1209
|
+
仅在以下条件同时满足时写入:
|
|
1210
|
+
1. STATE_FILE 环境变量已配置
|
|
1211
|
+
2. state 文件存在
|
|
1212
|
+
3. 当前项目在 state 中的 chat_id 为空
|
|
1213
|
+
"""
|
|
1214
|
+
state_file_env = os.environ.get("STATE_FILE")
|
|
1215
|
+
if not state_file_env:
|
|
1216
|
+
return
|
|
1217
|
+
|
|
1218
|
+
state_path = Path(state_file_env).expanduser()
|
|
1219
|
+
if not state_path.exists():
|
|
1220
|
+
worker_log.debug(
|
|
1221
|
+
"STATE_FILE 不存在,跳过自动记录 chat_id",
|
|
1222
|
+
extra={**_session_extra(), "path": str(state_path)},
|
|
1223
|
+
)
|
|
1224
|
+
return
|
|
1225
|
+
|
|
1226
|
+
# 使用文件锁保证并发安全
|
|
1227
|
+
lock_path = state_path.with_suffix(state_path.suffix + ".lock")
|
|
1228
|
+
import fcntl
|
|
1229
|
+
|
|
1230
|
+
try:
|
|
1231
|
+
with open(lock_path, "w", encoding="utf-8") as lock_file:
|
|
1232
|
+
fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
|
|
1233
|
+
|
|
1234
|
+
try:
|
|
1235
|
+
# 读取当前 state
|
|
1236
|
+
raw_state = json.loads(state_path.read_text(encoding="utf-8"))
|
|
1237
|
+
if not isinstance(raw_state, dict):
|
|
1238
|
+
worker_log.warning(
|
|
1239
|
+
"STATE_FILE 格式异常,跳过自动记录",
|
|
1240
|
+
extra=_session_extra(),
|
|
1241
|
+
)
|
|
1242
|
+
return
|
|
1243
|
+
|
|
1244
|
+
# 检查当前项目的 chat_id
|
|
1245
|
+
project_key = PROJECT_SLUG or PROJECT_NAME
|
|
1246
|
+
if not project_key:
|
|
1247
|
+
worker_log.warning(
|
|
1248
|
+
"PROJECT_SLUG 和 PROJECT_NAME 均未设置,跳过自动记录",
|
|
1249
|
+
extra=_session_extra(),
|
|
1250
|
+
)
|
|
1251
|
+
return
|
|
1252
|
+
|
|
1253
|
+
project_state = raw_state.get(project_key)
|
|
1254
|
+
if not isinstance(project_state, dict):
|
|
1255
|
+
# 项目不存在,创建新条目
|
|
1256
|
+
raw_state[project_key] = {
|
|
1257
|
+
"chat_id": chat_id,
|
|
1258
|
+
"model": ACTIVE_MODEL or "codex",
|
|
1259
|
+
"status": "running",
|
|
1260
|
+
}
|
|
1261
|
+
need_write = True
|
|
1262
|
+
elif project_state.get("chat_id") is None:
|
|
1263
|
+
# chat_id 为空,更新
|
|
1264
|
+
project_state["chat_id"] = chat_id
|
|
1265
|
+
need_write = True
|
|
1266
|
+
else:
|
|
1267
|
+
# chat_id 已存在,无需更新
|
|
1268
|
+
need_write = False
|
|
1269
|
+
|
|
1270
|
+
if need_write:
|
|
1271
|
+
# 写入更新后的 state
|
|
1272
|
+
tmp_path = state_path.with_suffix(state_path.suffix + ".tmp")
|
|
1273
|
+
tmp_path.write_text(
|
|
1274
|
+
json.dumps(raw_state, ensure_ascii=False, indent=4),
|
|
1275
|
+
encoding="utf-8",
|
|
1276
|
+
)
|
|
1277
|
+
tmp_path.replace(state_path)
|
|
1278
|
+
worker_log.info(
|
|
1279
|
+
"已自动记录 chat_id=%s 到 state 文件",
|
|
1280
|
+
chat_id,
|
|
1281
|
+
extra={**_session_extra(), "project": project_key},
|
|
1282
|
+
)
|
|
1283
|
+
else:
|
|
1284
|
+
worker_log.debug(
|
|
1285
|
+
"chat_id 已存在,跳过自动记录",
|
|
1286
|
+
extra={**_session_extra(), "existing_chat_id": project_state.get("chat_id")},
|
|
1287
|
+
)
|
|
1288
|
+
|
|
1289
|
+
except json.JSONDecodeError as exc:
|
|
1290
|
+
worker_log.error(
|
|
1291
|
+
"STATE_FILE 解析失败,跳过自动记录:%s",
|
|
1292
|
+
exc,
|
|
1293
|
+
extra=_session_extra(),
|
|
1294
|
+
)
|
|
1295
|
+
except Exception as exc:
|
|
1296
|
+
worker_log.error(
|
|
1297
|
+
"自动记录 chat_id 失败:%s",
|
|
1298
|
+
exc,
|
|
1299
|
+
extra={**_session_extra(), "chat": chat_id},
|
|
1300
|
+
)
|
|
1301
|
+
finally:
|
|
1302
|
+
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
|
|
1303
|
+
except Exception as exc:
|
|
1304
|
+
worker_log.error(
|
|
1305
|
+
"获取文件锁失败:%s",
|
|
1306
|
+
exc,
|
|
1307
|
+
extra=_session_extra(),
|
|
1308
|
+
)
|
|
1309
|
+
finally:
|
|
1310
|
+
# 清理锁文件
|
|
1311
|
+
try:
|
|
1312
|
+
if lock_path.exists():
|
|
1313
|
+
lock_path.unlink()
|
|
1314
|
+
except Exception:
|
|
1315
|
+
pass
|
|
1316
|
+
|
|
1317
|
+
|
|
1203
1318
|
async def _broadcast_worker_keyboard(bot: Bot) -> None:
|
|
1204
1319
|
"""启动时主动推送菜单,确保 Telegram 键盘同步。"""
|
|
1205
1320
|
targets = _resolve_worker_target_chat_ids()
|
|
@@ -2654,7 +2769,8 @@ def _build_summary_prompt(
|
|
|
2654
2769
|
) -> str:
|
|
2655
2770
|
"""构造模型摘要提示词,要求携带请求标识。"""
|
|
2656
2771
|
|
|
2657
|
-
|
|
2772
|
+
# 摘要提示词是发送给模型的,使用纯文本格式,不需要 Markdown 转义
|
|
2773
|
+
task_code = f"/{task.id}" if task.id else "-"
|
|
2658
2774
|
title = task.title or "-"
|
|
2659
2775
|
status_label = STATUS_LABELS.get(task.status, task.status)
|
|
2660
2776
|
note_lines: list[str] = []
|
|
@@ -2817,8 +2933,9 @@ def _build_task_history_view(
|
|
|
2817
2933
|
if total_items == 0:
|
|
2818
2934
|
raise ValueError("暂无事件记录")
|
|
2819
2935
|
|
|
2936
|
+
# 历史记录会被包裹在代码块中显示,使用纯文本格式,不需要 Markdown 转义
|
|
2820
2937
|
title_text = normalize_newlines(task.title or "").strip() or "-"
|
|
2821
|
-
title_display =
|
|
2938
|
+
title_display = title_text
|
|
2822
2939
|
|
|
2823
2940
|
digit_width = len(str(max(total_items, 1)))
|
|
2824
2941
|
placeholder_page = "9" * digit_width
|
|
@@ -3050,6 +3167,9 @@ CHAT_DELIVERED_HASHES: Dict[int, Dict[str, set[str]]] = {}
|
|
|
3050
3167
|
CHAT_DELIVERED_OFFSETS: Dict[int, Dict[str, set[int]]] = {}
|
|
3051
3168
|
CHAT_REPLY_COUNT: Dict[int, Dict[str, int]] = {}
|
|
3052
3169
|
CHAT_COMPACT_STATE: Dict[int, Dict[str, Dict[str, Any]]] = {}
|
|
3170
|
+
# 长轮询状态:用于延迟轮询机制
|
|
3171
|
+
CHAT_LONG_POLL_STATE: Dict[int, Dict[str, Any]] = {}
|
|
3172
|
+
CHAT_LONG_POLL_LOCK: Optional[asyncio.Lock] = None # 在事件循环启动后初始化
|
|
3053
3173
|
SUMMARY_REQUEST_TIMEOUT_SECONDS = 300.0
|
|
3054
3174
|
|
|
3055
3175
|
|
|
@@ -3644,7 +3764,19 @@ def _get_delivered_offsets(chat_id: int, session_key: str) -> set[int]:
|
|
|
3644
3764
|
return CHAT_DELIVERED_OFFSETS.setdefault(chat_id, {}).setdefault(session_key, set())
|
|
3645
3765
|
|
|
3646
3766
|
|
|
3647
|
-
async def _deliver_pending_messages(
|
|
3767
|
+
async def _deliver_pending_messages(
|
|
3768
|
+
chat_id: int,
|
|
3769
|
+
session_path: Path,
|
|
3770
|
+
*,
|
|
3771
|
+
add_completion_header: bool = True
|
|
3772
|
+
) -> bool:
|
|
3773
|
+
"""发送待处理的模型消息。
|
|
3774
|
+
|
|
3775
|
+
Args:
|
|
3776
|
+
chat_id: Telegram 聊天 ID
|
|
3777
|
+
session_path: 会话文件路径
|
|
3778
|
+
add_completion_header: 是否添加"✅模型执行完成"前缀(快速轮询阶段为 True,延迟轮询为 False)
|
|
3779
|
+
"""
|
|
3648
3780
|
session_key = str(session_path)
|
|
3649
3781
|
previous_offset = SESSION_OFFSETS.get(session_key, 0)
|
|
3650
3782
|
new_offset, events = _read_session_events(session_path)
|
|
@@ -3719,7 +3851,8 @@ async def _deliver_pending_messages(chat_id: int, session_path: Path) -> bool:
|
|
|
3719
3851
|
last_committed_offset = event_offset
|
|
3720
3852
|
SESSION_OFFSETS[session_key] = event_offset
|
|
3721
3853
|
continue
|
|
3722
|
-
|
|
3854
|
+
# 根据轮询阶段决定是否添加完成前缀
|
|
3855
|
+
formatted_text = _prepend_completion_header(text_to_send) if add_completion_header else text_to_send
|
|
3723
3856
|
payload_for_hash = _prepare_model_payload(formatted_text)
|
|
3724
3857
|
initial_hash = hashlib.sha256(payload_for_hash.encode("utf-8", errors="ignore")).hexdigest()
|
|
3725
3858
|
if initial_hash in delivered_hashes:
|
|
@@ -3830,22 +3963,24 @@ async def _deliver_pending_messages(chat_id: int, session_path: Path) -> bool:
|
|
|
3830
3963
|
SESSION_OFFSETS[session_key] = max(last_committed_offset, new_offset)
|
|
3831
3964
|
|
|
3832
3965
|
if delivered_response:
|
|
3966
|
+
# 实际发送了消息,返回 True 表示本次调用成功发送
|
|
3967
|
+
# 这样可以确保延迟轮询机制被正确触发
|
|
3833
3968
|
if ENABLE_PLAN_PROGRESS and plan_active:
|
|
3834
3969
|
worker_log.info(
|
|
3835
|
-
"
|
|
3970
|
+
"模型输出已发送,但计划仍在更新",
|
|
3971
|
+
extra={
|
|
3972
|
+
**_session_extra(path=session_path),
|
|
3973
|
+
"chat": chat_id,
|
|
3974
|
+
},
|
|
3975
|
+
)
|
|
3976
|
+
else:
|
|
3977
|
+
worker_log.info(
|
|
3978
|
+
"模型输出已发送且计划完成",
|
|
3836
3979
|
extra={
|
|
3837
3980
|
**_session_extra(path=session_path),
|
|
3838
3981
|
"chat": chat_id,
|
|
3839
3982
|
},
|
|
3840
3983
|
)
|
|
3841
|
-
return False
|
|
3842
|
-
worker_log.info(
|
|
3843
|
-
"模型输出已发送且计划完成",
|
|
3844
|
-
extra={
|
|
3845
|
-
**_session_extra(path=session_path),
|
|
3846
|
-
"chat": chat_id,
|
|
3847
|
-
},
|
|
3848
|
-
)
|
|
3849
3984
|
return True
|
|
3850
3985
|
|
|
3851
3986
|
if ENABLE_PLAN_PROGRESS and not plan_active and final_response_sent:
|
|
@@ -3992,6 +4127,9 @@ async def _ensure_session_watcher(chat_id: int) -> Optional[Path]:
|
|
|
3992
4127
|
if watcher is not None and watcher.done():
|
|
3993
4128
|
CHAT_WATCHERS.pop(chat_id, None)
|
|
3994
4129
|
|
|
4130
|
+
# 中断旧的延迟轮询(如果存在)
|
|
4131
|
+
await _interrupt_long_poll(chat_id)
|
|
4132
|
+
|
|
3995
4133
|
CHAT_WATCHERS[chat_id] = asyncio.create_task(
|
|
3996
4134
|
_watch_and_notify(
|
|
3997
4135
|
chat_id,
|
|
@@ -4151,23 +4289,181 @@ async def _finalize_plan_progress(chat_id: int) -> None:
|
|
|
4151
4289
|
|
|
4152
4290
|
|
|
4153
4291
|
|
|
4292
|
+
async def _interrupt_long_poll(chat_id: int) -> None:
|
|
4293
|
+
"""
|
|
4294
|
+
中断指定 chat 的延迟轮询。
|
|
4295
|
+
|
|
4296
|
+
当用户发送新消息时调用,确保旧的延迟轮询被终止,
|
|
4297
|
+
为新的监听任务让路。
|
|
4298
|
+
|
|
4299
|
+
线程安全:使用 asyncio.Lock 保护状态访问。
|
|
4300
|
+
"""
|
|
4301
|
+
if CHAT_LONG_POLL_LOCK is None:
|
|
4302
|
+
# 锁未初始化(测试环境或启动早期)
|
|
4303
|
+
return
|
|
4304
|
+
|
|
4305
|
+
async with CHAT_LONG_POLL_LOCK:
|
|
4306
|
+
state = CHAT_LONG_POLL_STATE.get(chat_id)
|
|
4307
|
+
if state is not None:
|
|
4308
|
+
state["interrupted"] = True
|
|
4309
|
+
worker_log.info(
|
|
4310
|
+
"标记延迟轮询为待中断",
|
|
4311
|
+
extra={"chat": chat_id},
|
|
4312
|
+
)
|
|
4313
|
+
|
|
4314
|
+
|
|
4154
4315
|
async def _watch_and_notify(chat_id: int, session_path: Path,
|
|
4155
4316
|
max_wait: float, interval: float):
|
|
4317
|
+
"""
|
|
4318
|
+
监听会话文件并发送消息。
|
|
4319
|
+
|
|
4320
|
+
两阶段轮询机制:
|
|
4321
|
+
- 阶段1(快速轮询):interval 间隔(通常 0.3 秒),直到首次发送成功
|
|
4322
|
+
- 阶段2(延迟轮询):3 秒间隔,最多 600 次(持续 30 分钟),捕获长时间任务的后续输出
|
|
4323
|
+
|
|
4324
|
+
异常安全:使用 try...finally 确保状态清理。
|
|
4325
|
+
中断机制:收到新 Telegram 消息时会设置 interrupted 标志,轮询自动停止。
|
|
4326
|
+
"""
|
|
4156
4327
|
start = time.monotonic()
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4328
|
+
first_delivery_done = False
|
|
4329
|
+
current_interval = interval # 初始为快速轮询间隔(0.3 秒)
|
|
4330
|
+
long_poll_rounds = 0
|
|
4331
|
+
long_poll_max_rounds = 600 # 30 分钟 / 3 秒 = 600 次
|
|
4332
|
+
long_poll_interval = 3.0 # 3 秒
|
|
4333
|
+
|
|
4334
|
+
try:
|
|
4335
|
+
while True:
|
|
4336
|
+
# 检查是否被新消息中断(使用锁保护)
|
|
4337
|
+
if CHAT_LONG_POLL_LOCK is not None:
|
|
4338
|
+
async with CHAT_LONG_POLL_LOCK:
|
|
4339
|
+
state = CHAT_LONG_POLL_STATE.get(chat_id)
|
|
4340
|
+
if state is not None and state.get("interrupted", False):
|
|
4341
|
+
worker_log.info(
|
|
4342
|
+
"延迟轮询被新消息中断",
|
|
4343
|
+
extra={
|
|
4344
|
+
**_session_extra(path=session_path),
|
|
4345
|
+
"chat": chat_id,
|
|
4346
|
+
"round": long_poll_rounds,
|
|
4347
|
+
},
|
|
4348
|
+
)
|
|
4349
|
+
return
|
|
4350
|
+
|
|
4351
|
+
await asyncio.sleep(current_interval)
|
|
4352
|
+
|
|
4353
|
+
# 检查超时(仅在快速轮询阶段)
|
|
4354
|
+
if not first_delivery_done and max_wait > 0 and time.monotonic() - start > max_wait:
|
|
4355
|
+
worker_log.warning(
|
|
4356
|
+
"[session-map] chat=%s 长时间未获取到 Codex 输出,停止轮询",
|
|
4357
|
+
chat_id,
|
|
4358
|
+
extra=_session_extra(path=session_path),
|
|
4359
|
+
)
|
|
4360
|
+
return
|
|
4361
|
+
|
|
4362
|
+
if not session_path.exists():
|
|
4363
|
+
continue
|
|
4364
|
+
|
|
4365
|
+
try:
|
|
4366
|
+
# 快速轮询阶段添加前缀,延迟轮询阶段不添加
|
|
4367
|
+
delivered = await _deliver_pending_messages(
|
|
4368
|
+
chat_id,
|
|
4369
|
+
session_path,
|
|
4370
|
+
add_completion_header=not first_delivery_done
|
|
4371
|
+
)
|
|
4372
|
+
except Exception as exc:
|
|
4373
|
+
worker_log.error(
|
|
4374
|
+
"消息发送时发生未预期异常",
|
|
4375
|
+
exc_info=exc,
|
|
4376
|
+
extra={
|
|
4377
|
+
**_session_extra(path=session_path),
|
|
4378
|
+
"chat": chat_id,
|
|
4379
|
+
},
|
|
4380
|
+
)
|
|
4381
|
+
delivered = False
|
|
4382
|
+
|
|
4383
|
+
# 首次发送成功,切换到延迟轮询模式
|
|
4384
|
+
if delivered and not first_delivery_done:
|
|
4385
|
+
first_delivery_done = True
|
|
4386
|
+
current_interval = long_poll_interval
|
|
4387
|
+
if CHAT_LONG_POLL_LOCK is not None:
|
|
4388
|
+
async with CHAT_LONG_POLL_LOCK:
|
|
4389
|
+
CHAT_LONG_POLL_STATE[chat_id] = {
|
|
4390
|
+
"active": True,
|
|
4391
|
+
"round": 0,
|
|
4392
|
+
"max_rounds": long_poll_max_rounds,
|
|
4393
|
+
"interrupted": False,
|
|
4394
|
+
}
|
|
4395
|
+
worker_log.info(
|
|
4396
|
+
"首次发送成功,启动延迟轮询模式",
|
|
4397
|
+
extra={
|
|
4398
|
+
**_session_extra(path=session_path),
|
|
4399
|
+
"chat": chat_id,
|
|
4400
|
+
"interval": long_poll_interval,
|
|
4401
|
+
"max_rounds": long_poll_max_rounds,
|
|
4402
|
+
},
|
|
4403
|
+
)
|
|
4404
|
+
continue
|
|
4405
|
+
|
|
4406
|
+
# 延迟轮询阶段
|
|
4407
|
+
if first_delivery_done:
|
|
4408
|
+
if delivered:
|
|
4409
|
+
# 又收到新消息,重置轮询计数
|
|
4410
|
+
long_poll_rounds = 0
|
|
4411
|
+
if CHAT_LONG_POLL_LOCK is not None:
|
|
4412
|
+
async with CHAT_LONG_POLL_LOCK:
|
|
4413
|
+
state = CHAT_LONG_POLL_STATE.get(chat_id)
|
|
4414
|
+
if state is not None:
|
|
4415
|
+
state["round"] = 0
|
|
4416
|
+
worker_log.info(
|
|
4417
|
+
"延迟轮询中收到新消息,重置计数",
|
|
4418
|
+
extra={
|
|
4419
|
+
**_session_extra(path=session_path),
|
|
4420
|
+
"chat": chat_id,
|
|
4421
|
+
},
|
|
4422
|
+
)
|
|
4423
|
+
else:
|
|
4424
|
+
# 无新消息,增加轮询计数
|
|
4425
|
+
long_poll_rounds += 1
|
|
4426
|
+
if CHAT_LONG_POLL_LOCK is not None:
|
|
4427
|
+
async with CHAT_LONG_POLL_LOCK:
|
|
4428
|
+
state = CHAT_LONG_POLL_STATE.get(chat_id)
|
|
4429
|
+
if state is not None:
|
|
4430
|
+
state["round"] = long_poll_rounds
|
|
4431
|
+
|
|
4432
|
+
if long_poll_rounds >= long_poll_max_rounds:
|
|
4433
|
+
worker_log.info(
|
|
4434
|
+
"延迟轮询达到最大次数,停止监听",
|
|
4435
|
+
extra={
|
|
4436
|
+
**_session_extra(path=session_path),
|
|
4437
|
+
"chat": chat_id,
|
|
4438
|
+
"total_rounds": long_poll_rounds,
|
|
4439
|
+
},
|
|
4440
|
+
)
|
|
4441
|
+
return
|
|
4442
|
+
|
|
4443
|
+
worker_log.debug(
|
|
4444
|
+
"延迟轮询中无新消息",
|
|
4445
|
+
extra={
|
|
4446
|
+
**_session_extra(path=session_path),
|
|
4447
|
+
"chat": chat_id,
|
|
4448
|
+
"round": f"{long_poll_rounds}/{long_poll_max_rounds}",
|
|
4449
|
+
},
|
|
4450
|
+
)
|
|
4451
|
+
continue
|
|
4452
|
+
|
|
4453
|
+
# 快速轮询阶段:如果已发送消息,退出
|
|
4454
|
+
if delivered:
|
|
4455
|
+
return
|
|
4456
|
+
|
|
4457
|
+
finally:
|
|
4458
|
+
# 确保无论如何都清理延迟轮询状态
|
|
4459
|
+
if CHAT_LONG_POLL_LOCK is not None:
|
|
4460
|
+
async with CHAT_LONG_POLL_LOCK:
|
|
4461
|
+
if chat_id in CHAT_LONG_POLL_STATE:
|
|
4462
|
+
CHAT_LONG_POLL_STATE.pop(chat_id, None)
|
|
4463
|
+
worker_log.debug(
|
|
4464
|
+
"监听任务退出,已清理延迟轮询状态",
|
|
4465
|
+
extra={"chat": chat_id},
|
|
4466
|
+
)
|
|
4171
4467
|
|
|
4172
4468
|
|
|
4173
4469
|
def _read_pointer_path(pointer: Path) -> Optional[Path]:
|
|
@@ -5422,7 +5718,7 @@ async def on_task_desc_input(message: Message, state: FSMContext) -> None:
|
|
|
5422
5718
|
|
|
5423
5719
|
@router.message(TaskDescriptionStates.waiting_confirm)
|
|
5424
5720
|
async def on_task_desc_confirm_stage_text(message: Message, state: FSMContext) -> None:
|
|
5425
|
-
"""
|
|
5721
|
+
"""处理任务描述确认阶段的菜单指令。支持按钮点击、数字编号和直接文本输入。"""
|
|
5426
5722
|
|
|
5427
5723
|
data = await state.get_data()
|
|
5428
5724
|
task_id = data.get("task_id")
|
|
@@ -5431,13 +5727,19 @@ async def on_task_desc_confirm_stage_text(message: Message, state: FSMContext) -
|
|
|
5431
5727
|
await message.answer("会话已失效,请重新操作。", reply_markup=_build_worker_main_keyboard())
|
|
5432
5728
|
return
|
|
5433
5729
|
|
|
5434
|
-
|
|
5435
|
-
|
|
5730
|
+
# 使用 _resolve_reply_choice() 智能解析用户输入,支持数字编号、按钮文本和直接文本
|
|
5731
|
+
options = [TASK_DESC_CONFIRM_TEXT, TASK_DESC_RETRY_TEXT, TASK_DESC_CANCEL_TEXT]
|
|
5732
|
+
resolved = _resolve_reply_choice(message.text, options=options)
|
|
5733
|
+
stripped = _strip_number_prefix((message.text or "").strip()).lower()
|
|
5734
|
+
|
|
5735
|
+
# 处理取消操作
|
|
5736
|
+
if resolved == options[2] or _is_cancel_message(resolved) or stripped in {"取消"}:
|
|
5436
5737
|
await state.clear()
|
|
5437
5738
|
await message.answer("已取消编辑任务描述。", reply_markup=_build_worker_main_keyboard())
|
|
5438
5739
|
return
|
|
5439
5740
|
|
|
5440
|
-
|
|
5741
|
+
# 处理重新输入操作
|
|
5742
|
+
if resolved == options[1] or stripped in {"重新输入"}:
|
|
5441
5743
|
task = await TASK_SERVICE.get_task(task_id)
|
|
5442
5744
|
if task is None:
|
|
5443
5745
|
await state.clear()
|
|
@@ -5455,7 +5757,8 @@ async def on_task_desc_confirm_stage_text(message: Message, state: FSMContext) -
|
|
|
5455
5757
|
)
|
|
5456
5758
|
return
|
|
5457
5759
|
|
|
5458
|
-
|
|
5760
|
+
# 处理确认更新操作
|
|
5761
|
+
if resolved == options[0] or stripped in {"确认", "确认更新"}:
|
|
5459
5762
|
new_description = data.get("new_description")
|
|
5460
5763
|
if new_description is None:
|
|
5461
5764
|
await state.set_state(TaskDescriptionStates.waiting_content)
|
|
@@ -5486,8 +5789,9 @@ async def on_task_desc_confirm_stage_text(message: Message, state: FSMContext) -
|
|
|
5486
5789
|
)
|
|
5487
5790
|
return
|
|
5488
5791
|
|
|
5792
|
+
# 无效输入,提示用户
|
|
5489
5793
|
await message.answer(
|
|
5490
|
-
"
|
|
5794
|
+
"当前处于确认阶段,请选择确认、重新输入或取消,可直接输入编号或点击键盘按钮:",
|
|
5491
5795
|
reply_markup=_build_task_desc_confirm_keyboard(),
|
|
5492
5796
|
)
|
|
5493
5797
|
|
|
@@ -6749,6 +7053,9 @@ async def on_edit_new_value(message: Message, state: FSMContext) -> None:
|
|
|
6749
7053
|
|
|
6750
7054
|
@router.message(CommandStart())
|
|
6751
7055
|
async def on_start(m: Message):
|
|
7056
|
+
# 首次收到消息时自动记录 chat_id 到 state 文件
|
|
7057
|
+
_auto_record_chat_id(m.chat.id)
|
|
7058
|
+
|
|
6752
7059
|
await m.answer(
|
|
6753
7060
|
(
|
|
6754
7061
|
f"Hello, {m.from_user.full_name}!\n"
|
|
@@ -6764,6 +7071,9 @@ async def on_start(m: Message):
|
|
|
6764
7071
|
|
|
6765
7072
|
@router.message(F.text)
|
|
6766
7073
|
async def on_text(m: Message):
|
|
7074
|
+
# 首次收到消息时自动记录 chat_id 到 state 文件
|
|
7075
|
+
_auto_record_chat_id(m.chat.id)
|
|
7076
|
+
|
|
6767
7077
|
prompt = (m.text or "").strip()
|
|
6768
7078
|
if not prompt:
|
|
6769
7079
|
return await m.answer("请输入非空提示词")
|
|
@@ -6869,7 +7179,9 @@ async def _ensure_worker_menu_button(bot: Bot) -> None:
|
|
|
6869
7179
|
)
|
|
6870
7180
|
|
|
6871
7181
|
async def main():
|
|
6872
|
-
global _bot
|
|
7182
|
+
global _bot, CHAT_LONG_POLL_LOCK
|
|
7183
|
+
# 初始化长轮询锁
|
|
7184
|
+
CHAT_LONG_POLL_LOCK = asyncio.Lock()
|
|
6873
7185
|
_bot = build_bot()
|
|
6874
7186
|
try:
|
|
6875
7187
|
await ensure_telegram_connectivity(_bot)
|
scripts/bump_version.sh
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 版本管理便捷脚本
|
|
3
|
+
# 使用方式:
|
|
4
|
+
# ./scripts/bump_version.sh patch
|
|
5
|
+
# ./scripts/bump_version.sh minor
|
|
6
|
+
# ./scripts/bump_version.sh major
|
|
7
|
+
# ./scripts/bump_version.sh show
|
|
8
|
+
# ./scripts/bump_version.sh --help
|
|
9
|
+
|
|
10
|
+
set -e
|
|
11
|
+
|
|
12
|
+
# 项目根目录
|
|
13
|
+
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
14
|
+
cd "$PROJECT_ROOT"
|
|
15
|
+
|
|
16
|
+
# bump-my-version 路径
|
|
17
|
+
BUMP_CMD="/Users/david/.config/vibego/runtime/.venv/bin/bump-my-version"
|
|
18
|
+
|
|
19
|
+
# 检查 bump-my-version 是否存在
|
|
20
|
+
if [ ! -f "$BUMP_CMD" ]; then
|
|
21
|
+
echo "错误:找不到 bump-my-version"
|
|
22
|
+
echo "请先安装:pip install bump-my-version"
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# 如果没有参数,显示帮助
|
|
27
|
+
if [ $# -eq 0 ]; then
|
|
28
|
+
echo "用法:"
|
|
29
|
+
echo " $0 patch 递增补丁版本 (0.2.11 → 0.2.12)"
|
|
30
|
+
echo " 自动提交:fix: bugfixes"
|
|
31
|
+
echo " $0 minor 递增次版本 (0.2.11 → 0.3.0)"
|
|
32
|
+
echo " 自动提交:feat: 添加新功能"
|
|
33
|
+
echo " $0 major 递增主版本 (0.2.11 → 1.0.0)"
|
|
34
|
+
echo " 自动提交:feat!: 重大变更"
|
|
35
|
+
echo " $0 show 显示当前版本"
|
|
36
|
+
echo " $0 --dry-run 预览变更(添加在 patch/minor/major 后)"
|
|
37
|
+
echo ""
|
|
38
|
+
echo "说明:"
|
|
39
|
+
echo " 脚本会自动提交当前未提交的修改,然后递增版本号。"
|
|
40
|
+
echo " 如果不想自动提交,请在参数中添加 --no-auto-commit"
|
|
41
|
+
echo ""
|
|
42
|
+
echo "示例:"
|
|
43
|
+
echo " $0 patch # 自动提交修改并递增补丁版本"
|
|
44
|
+
echo " $0 patch --dry-run # 预览补丁版本递增(不会提交)"
|
|
45
|
+
echo " $0 minor --no-auto-commit # 仅递增版本,不自动提交当前修改"
|
|
46
|
+
exit 0
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# 处理 show 命令
|
|
50
|
+
if [ "$1" = "show" ]; then
|
|
51
|
+
"$BUMP_CMD" show current_version
|
|
52
|
+
exit 0
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# 处理 --help
|
|
56
|
+
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
|
57
|
+
"$BUMP_CMD" --help
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# 检查是否禁用自动提交
|
|
62
|
+
AUTO_COMMIT=true
|
|
63
|
+
if [[ "$*" =~ "--no-auto-commit" ]]; then
|
|
64
|
+
AUTO_COMMIT=false
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# 检查是否是 dry-run
|
|
68
|
+
DRY_RUN=false
|
|
69
|
+
if [[ "$*" =~ "--dry-run" ]]; then
|
|
70
|
+
DRY_RUN=true
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# 获取版本类型
|
|
74
|
+
VERSION_TYPE="$1"
|
|
75
|
+
|
|
76
|
+
# 获取对应版本类型的 commit 消息
|
|
77
|
+
get_commit_message() {
|
|
78
|
+
case "$1" in
|
|
79
|
+
patch)
|
|
80
|
+
echo "fix: bugfixes"
|
|
81
|
+
;;
|
|
82
|
+
minor)
|
|
83
|
+
echo "feat: 添加新功能"
|
|
84
|
+
;;
|
|
85
|
+
major)
|
|
86
|
+
echo "feat!: 重大变更"
|
|
87
|
+
;;
|
|
88
|
+
*)
|
|
89
|
+
echo ""
|
|
90
|
+
;;
|
|
91
|
+
esac
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# 检查版本类型是否有效
|
|
95
|
+
COMMIT_MSG=$(get_commit_message "$VERSION_TYPE")
|
|
96
|
+
if [ -z "$COMMIT_MSG" ]; then
|
|
97
|
+
# 如果不是有效的版本类型,直接传递给 bump-my-version
|
|
98
|
+
"$BUMP_CMD" bump "$@"
|
|
99
|
+
exit 0
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# 显示当前版本
|
|
103
|
+
echo "📦 当前版本:$("$BUMP_CMD" show current_version)"
|
|
104
|
+
echo ""
|
|
105
|
+
|
|
106
|
+
# 检查是否有未提交的修改
|
|
107
|
+
if [ "$AUTO_COMMIT" = true ] && [ "$DRY_RUN" = false ]; then
|
|
108
|
+
if ! git diff-index --quiet HEAD -- 2>/dev/null; then
|
|
109
|
+
echo "📝 检测到未提交的修改,准备创建 commit..."
|
|
110
|
+
echo ""
|
|
111
|
+
|
|
112
|
+
echo "Commit 消息:$COMMIT_MSG"
|
|
113
|
+
echo ""
|
|
114
|
+
|
|
115
|
+
# 显示将要提交的文件
|
|
116
|
+
echo "将要提交的文件:"
|
|
117
|
+
git status --short
|
|
118
|
+
echo ""
|
|
119
|
+
|
|
120
|
+
# 提交所有修改
|
|
121
|
+
git add .
|
|
122
|
+
git commit -m "$COMMIT_MSG"
|
|
123
|
+
|
|
124
|
+
echo "✅ 代码修改已提交"
|
|
125
|
+
echo ""
|
|
126
|
+
else
|
|
127
|
+
echo "ℹ️ 没有未提交的修改,跳过自动 commit"
|
|
128
|
+
echo ""
|
|
129
|
+
fi
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# 执行版本递增
|
|
133
|
+
echo "🚀 开始递增版本..."
|
|
134
|
+
echo ""
|
|
135
|
+
|
|
136
|
+
"$BUMP_CMD" bump "$@"
|
|
137
|
+
|
|
138
|
+
echo ""
|
|
139
|
+
echo "✅ 版本管理完成!"
|
|
140
|
+
echo ""
|
|
141
|
+
echo "📋 操作摘要:"
|
|
142
|
+
if [ "$AUTO_COMMIT" = true ] && [ "$DRY_RUN" = false ]; then
|
|
143
|
+
echo " 1. 已提交代码修改(如有)"
|
|
144
|
+
echo " 2. 已递增版本号"
|
|
145
|
+
echo " 3. 已创建版本 commit 和 tag"
|
|
146
|
+
else
|
|
147
|
+
echo " 1. 已递增版本号"
|
|
148
|
+
echo " 2. 已创建版本 commit 和 tag"
|
|
149
|
+
fi
|
|
150
|
+
echo ""
|
|
151
|
+
echo "💡 提示:如需推送到远程,请执行:"
|
|
152
|
+
echo " git push && git push --tags"
|
scripts/models/claudecode.sh
CHANGED
|
@@ -20,6 +20,10 @@ claude_project_key_from_workdir() {
|
|
|
20
20
|
model_configure() {
|
|
21
21
|
MODEL_NAME="ClaudeCode"
|
|
22
22
|
MODEL_WORKDIR="${CLAUDE_WORKDIR:-${MODEL_WORKDIR:-$ROOT_DIR}}"
|
|
23
|
+
# 默认关闭文件快照,避免孤儿 CLI 持续写入 jsonl
|
|
24
|
+
CLAUDE_DISABLE_FILE_CHECKPOINTING="${CLAUDE_DISABLE_FILE_CHECKPOINTING:-1}"
|
|
25
|
+
CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING="${CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING:-$CLAUDE_DISABLE_FILE_CHECKPOINTING}"
|
|
26
|
+
export CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING
|
|
23
27
|
local project_key
|
|
24
28
|
if [[ -n "${CLAUDE_PROJECT_KEY:-}" ]]; then
|
|
25
29
|
project_key="$CLAUDE_PROJECT_KEY"
|
scripts/run_bot.sh
CHANGED
|
@@ -151,6 +151,9 @@ export TMUX_LOG="$MODEL_LOG"
|
|
|
151
151
|
export PROJECT_NAME="$PROJECT_NAME"
|
|
152
152
|
export LOG_DIR="$LOG_DIR"
|
|
153
153
|
export ROOT_DIR="$SOURCE_ROOT"
|
|
154
|
+
if [[ -n "${CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING:-}" ]]; then
|
|
155
|
+
export CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING
|
|
156
|
+
fi
|
|
154
157
|
|
|
155
158
|
"$SOURCE_ROOT/scripts/start_tmux_codex.sh" --kill >/dev/null
|
|
156
159
|
|
scripts/start_tmux_codex.sh
CHANGED
|
@@ -117,13 +117,20 @@ run_tmux pipe-pane -o -t "$SESSION_NAME" "$PIPE_CMD"
|
|
|
117
117
|
|
|
118
118
|
# 同步环境变量到 tmux 服务端,避免复用旧会话时丢失设置
|
|
119
119
|
run_tmux set-environment -t "$SESSION_NAME" DISABLE_UPDATE_PROMPT "${DISABLE_UPDATE_PROMPT:-true}"
|
|
120
|
+
if [[ -n "${CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING:-}" ]]; then
|
|
121
|
+
run_tmux set-environment -t "$SESSION_NAME" CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING "${CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING}"
|
|
122
|
+
fi
|
|
120
123
|
|
|
121
124
|
if (( RESTART )); then
|
|
122
125
|
run_tmux send-keys -t "$SESSION_NAME" C-c
|
|
123
126
|
sleep 1
|
|
124
127
|
fi
|
|
125
128
|
|
|
126
|
-
printf
|
|
129
|
+
env_prefix="env $(printf '%q' "DISABLE_UPDATE_PROMPT=${DISABLE_UPDATE_PROMPT:-true}")"
|
|
130
|
+
if [[ -n "${CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING:-}" ]]; then
|
|
131
|
+
env_prefix+=" $(printf '%q' "CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING=${CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING}")"
|
|
132
|
+
fi
|
|
133
|
+
printf -v FINAL_CMD '%s %s' "$env_prefix" "$MODEL_CMD"
|
|
127
134
|
|
|
128
135
|
if (( SESSION_CREATED )) || (( FORCE_START )); then
|
|
129
136
|
run_tmux send-keys -t "$SESSION_NAME" "$FINAL_CMD" C-m
|
scripts/stop_bot.sh
CHANGED
|
@@ -47,6 +47,62 @@ fi
|
|
|
47
47
|
|
|
48
48
|
POINTER_BASENAME="${MODEL_POINTER_BASENAME:-current_session.txt}"
|
|
49
49
|
|
|
50
|
+
graceful_shutdown_claudecode() {
|
|
51
|
+
local session="$1"
|
|
52
|
+
local timeout="${2:-10}"
|
|
53
|
+
if ! command -v tmux >/dev/null 2>&1; then
|
|
54
|
+
return 0
|
|
55
|
+
fi
|
|
56
|
+
if ! tmux -u has-session -t "$session" >/dev/null 2>&1; then
|
|
57
|
+
return 0
|
|
58
|
+
fi
|
|
59
|
+
local pane_ids=()
|
|
60
|
+
while IFS= read -r pane; do
|
|
61
|
+
[[ -z "$pane" ]] && continue
|
|
62
|
+
pane_ids+=("$pane")
|
|
63
|
+
done < <(tmux -u list-panes -t "$session" -F "#{pane_id}" 2>/dev/null || true)
|
|
64
|
+
(( ${#pane_ids[@]} )) || return 0
|
|
65
|
+
|
|
66
|
+
local current_cmd has_claude=0
|
|
67
|
+
for pane in "${pane_ids[@]}"; do
|
|
68
|
+
current_cmd=$(tmux -u display-message -p -t "$pane" '#{pane_current_command}' 2>/dev/null || echo "")
|
|
69
|
+
if [[ "$current_cmd" == claude* ]]; then
|
|
70
|
+
has_claude=1
|
|
71
|
+
break
|
|
72
|
+
fi
|
|
73
|
+
done
|
|
74
|
+
(( has_claude )) || return 0
|
|
75
|
+
|
|
76
|
+
printf '[stop-bot] 检测到 ClaudeCode 会话,尝试发送 /exit (session=%s)\n' "$session"
|
|
77
|
+
for pane in "${pane_ids[@]}"; do
|
|
78
|
+
tmux -u send-keys -t "$pane" Escape
|
|
79
|
+
tmux -u send-keys -t "$pane" C-u
|
|
80
|
+
tmux -u send-keys -t "$pane" "/exit" C-m
|
|
81
|
+
done
|
|
82
|
+
|
|
83
|
+
local end_time=$(( $(date +%s) + timeout ))
|
|
84
|
+
while tmux -u has-session -t "$session" >/dev/null 2>&1; do
|
|
85
|
+
local still_running=0
|
|
86
|
+
for pane in "${pane_ids[@]}"; do
|
|
87
|
+
current_cmd=$(tmux -u display-message -p -t "$pane" '#{pane_current_command}' 2>/dev/null || echo "")
|
|
88
|
+
if [[ "$current_cmd" == claude* ]]; then
|
|
89
|
+
still_running=1
|
|
90
|
+
break
|
|
91
|
+
fi
|
|
92
|
+
done
|
|
93
|
+
if (( still_running == 0 )); then
|
|
94
|
+
printf '[stop-bot] ClaudeCode 会话已响应 /exit (session=%s)\n' "$session"
|
|
95
|
+
break
|
|
96
|
+
fi
|
|
97
|
+
if (( $(date +%s) >= end_time )); then
|
|
98
|
+
printf '[stop-bot] ClaudeCode /exit 超时,将继续执行强制关闭 (session=%s)\n' "$session" >&2
|
|
99
|
+
break
|
|
100
|
+
fi
|
|
101
|
+
sleep 0.5
|
|
102
|
+
done
|
|
103
|
+
return 0
|
|
104
|
+
}
|
|
105
|
+
|
|
50
106
|
kill_tty_sessions() {
|
|
51
107
|
local session="$1"
|
|
52
108
|
if command -v tmux >/dev/null 2>&1 && tmux -u has-session -t "$session" >/dev/null 2>&1; then
|
|
@@ -64,8 +120,10 @@ kill_pid_file() {
|
|
|
64
120
|
if [[ ! -f "$pid_file" ]]; then
|
|
65
121
|
local fallback_dir
|
|
66
122
|
fallback_dir="$(dirname "$pid_file")"
|
|
67
|
-
[[ -d "$fallback_dir" ]]
|
|
68
|
-
|
|
123
|
+
if [[ -d "$fallback_dir" ]]; then
|
|
124
|
+
clear_session_files "$fallback_dir"
|
|
125
|
+
fi
|
|
126
|
+
return 0
|
|
69
127
|
fi
|
|
70
128
|
local bot_pid
|
|
71
129
|
bot_pid=$(cat "$pid_file")
|
|
@@ -79,7 +137,10 @@ kill_pid_file() {
|
|
|
79
137
|
rm -f "$pid_file"
|
|
80
138
|
local pid_dir
|
|
81
139
|
pid_dir="$(dirname "$pid_file")"
|
|
82
|
-
[[ -d "$pid_dir" ]]
|
|
140
|
+
if [[ -d "$pid_dir" ]]; then
|
|
141
|
+
clear_session_files "$pid_dir"
|
|
142
|
+
fi
|
|
143
|
+
return 0
|
|
83
144
|
}
|
|
84
145
|
|
|
85
146
|
stop_single_worker() {
|
|
@@ -88,9 +149,11 @@ stop_single_worker() {
|
|
|
88
149
|
log_dir="$(log_dir_for "$model_name" "$project_name")"
|
|
89
150
|
pid_file="$log_dir/bot.pid"
|
|
90
151
|
tmux_session="$(tmux_session_for "$project_name")"
|
|
152
|
+
graceful_shutdown_claudecode "$tmux_session" 15 || true
|
|
91
153
|
kill_tty_sessions "$tmux_session"
|
|
92
154
|
kill_pid_file "$pid_file"
|
|
93
155
|
clear_session_files "$log_dir"
|
|
156
|
+
return 0
|
|
94
157
|
}
|
|
95
158
|
|
|
96
159
|
stop_all_workers() {
|
|
@@ -109,6 +172,7 @@ stop_all_workers() {
|
|
|
109
172
|
if [[ -n "$sessions" ]]; then
|
|
110
173
|
while IFS= read -r sess; do
|
|
111
174
|
[[ -z "$sess" ]] && continue
|
|
175
|
+
graceful_shutdown_claudecode "$sess" 15 || true
|
|
112
176
|
tmux -u kill-session -t "$sess" >/dev/null 2>&1 || true
|
|
113
177
|
stopped=1
|
|
114
178
|
done <<<"$sessions"
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
bot.py,sha256=
|
|
1
|
+
bot.py,sha256=YsMdqha8MaJrnDJQwZ0GgQFrnoYKctV5es-RmbL-VeU,262371
|
|
2
2
|
logging_setup.py,sha256=gvxHi8mUwK3IhXJrsGNTDo-DR6ngkyav1X-tvlBF_IE,4613
|
|
3
3
|
master.py,sha256=Qz2NTapUexVvpQz8Y_pVhKd-uXkqp3M6oclzfAzIuGs,106497
|
|
4
4
|
project_repository.py,sha256=UcthtSGOJK0cTE5bQCneo3xkomRG-kyc1N1QVqxeHIs,17577
|
|
5
5
|
scripts/__init__.py,sha256=LVrXUkvWKoc6Sb47X5G0gbIxu5aJ2ARW-qJ14vwi5vM,65
|
|
6
|
+
scripts/bump_version.sh,sha256=a4uB8V8Y5LPsoqTCdzQKsEE8HhwpBmqRaQInG52LDig,4089
|
|
6
7
|
scripts/log_writer.py,sha256=8euoMlRo7cbtHApbcEoJnwzLABxti-ovJWFLRN1oDQw,3843
|
|
7
8
|
scripts/master_healthcheck.py,sha256=-X0VVsZ0AXaOb7izxTO_oyu23g_1jsirNdGIcP8nrSI,8321
|
|
8
9
|
scripts/requirements.txt,sha256=QSt30DSSSHtfucTFPpc7twk9kLS5rVLNTcvDiagxrZg,62
|
|
9
|
-
scripts/run_bot.sh,sha256=
|
|
10
|
+
scripts/run_bot.sh,sha256=rN4K1nz041XBaUJmnBBKHS2cHmQf11vPNX8wf1hbVR4,4596
|
|
10
11
|
scripts/start.sh,sha256=IsNjFWXX3qmFxx_7iiVEidTj9VHBUTkNNbsCPoNSL0E,6782
|
|
11
|
-
scripts/start_tmux_codex.sh,sha256=
|
|
12
|
+
scripts/start_tmux_codex.sh,sha256=xyLv29p924q-ysxvZYAP3T6VrqLPBPMBWo9QP7cuL50,4438
|
|
12
13
|
scripts/stop_all.sh,sha256=FOz07gi2CI9sMHxBb8XkqHtxRYs3jt1RYgGrEi-htVg,4086
|
|
13
|
-
scripts/stop_bot.sh,sha256=
|
|
14
|
+
scripts/stop_bot.sh,sha256=ot6Sm0IYoXuRNslUVEflQmJKu5Agm-5xIUXXudJWyTM,5588
|
|
14
15
|
scripts/test_deps_check.sh,sha256=AeSTucbNuNdOPSGvqVp0m31TVFDb0DmU8gSKg-Y5z2I,1696
|
|
15
16
|
scripts/.venv/lib/python3.14/site-packages/pip/__init__.py,sha256=_lgs5Mfp0t7AGtI7sTVwxKWquz_vahWWH9kKO1cJusA,353
|
|
16
17
|
scripts/.venv/lib/python3.14/site-packages/pip/__main__.py,sha256=WzbhHXTbSE6gBY19mNN9m4s5o_365LOvTYSgqgbdBhE,854
|
|
@@ -414,7 +415,7 @@ scripts/.venv/lib/python3.14/site-packages/pip/_vendor/urllib3/util/ssltransport
|
|
|
414
415
|
scripts/.venv/lib/python3.14/site-packages/pip/_vendor/urllib3/util/timeout.py,sha256=cwq4dMk87mJHSBktK1miYJ-85G-3T3RmT20v7SFCpno,10168
|
|
415
416
|
scripts/.venv/lib/python3.14/site-packages/pip/_vendor/urllib3/util/url.py,sha256=lCAE7M5myA8EDdW0sJuyyZhVB9K_j38ljWhHAnFaWoE,14296
|
|
416
417
|
scripts/.venv/lib/python3.14/site-packages/pip/_vendor/urllib3/util/wait.py,sha256=fOX0_faozG2P7iVojQoE1mbydweNyTcm-hXEfFrTtLI,5403
|
|
417
|
-
scripts/models/claudecode.sh,sha256=
|
|
418
|
+
scripts/models/claudecode.sh,sha256=k8153NH6O925VvhXh2rywVRje9CyUyIurn42EetZHEI,1526
|
|
418
419
|
scripts/models/codex.sh,sha256=_vp-v4L4eNz20JC2JIw1npE0Qz-HBUpdzyxLAtqridU,457
|
|
419
420
|
scripts/models/common.sh,sha256=Xx6BaVqWSxpZUUHf6s6u_rkfbNts0UePeA9J9uoC_HA,2168
|
|
420
421
|
scripts/models/gemini.sh,sha256=0fychnJL4uQJ1_wuc8O2sAQ4K0q3v7_Ku_9EOgUdocI,449
|
|
@@ -424,14 +425,14 @@ tasks/constants.py,sha256=tS1kZxBIUm3JJUMHm25XI-KHNUZl5NhbbuzjzL_rF-c,299
|
|
|
424
425
|
tasks/fsm.py,sha256=rKXXLEieQQU4r2z_CZUvn1_70FXiZXBBugF40gpe_tQ,1476
|
|
425
426
|
tasks/models.py,sha256=N_qqRBo9xMSV0vbn4k6bLBXT8C_dp_oTFUxvdx16ZQM,2459
|
|
426
427
|
tasks/service.py,sha256=w_S_aWiVqRXzXEpimLDsuCCCX2lB5uDkff9aKThBw9c,41916
|
|
427
|
-
vibego_cli/__init__.py,sha256=
|
|
428
|
+
vibego_cli/__init__.py,sha256=HDuRE3BFyJQ0KL0Ozp34-WoYBjtGdy6lzYEt1AS7vyY,311
|
|
428
429
|
vibego_cli/__main__.py,sha256=qqTrYmRRLe4361fMzbI3-CqpZ7AhTofIHmfp4ykrrBY,158
|
|
429
430
|
vibego_cli/config.py,sha256=33WSORCfUIxrDtgASPEbVqVLBVNHh-RSFLpNy7tfc0s,2992
|
|
430
431
|
vibego_cli/deps.py,sha256=1nRXI7Dd-S1hYE8DligzK5fIluQWETRUj4_OKL0DikQ,1419
|
|
431
432
|
vibego_cli/main.py,sha256=e2W5Pb9U9rfmF-jNX9uIA3222lhM0GgcvSdFTDBZd2s,12086
|
|
432
433
|
vibego_cli/data/worker_requirements.txt,sha256=QSt30DSSSHtfucTFPpc7twk9kLS5rVLNTcvDiagxrZg,62
|
|
433
|
-
vibego-0.2.
|
|
434
|
-
vibego-0.2.
|
|
435
|
-
vibego-0.2.
|
|
436
|
-
vibego-0.2.
|
|
437
|
-
vibego-0.2.
|
|
434
|
+
vibego-0.2.13.dist-info/METADATA,sha256=eaSYWnC-3iRHcqmq3ewae3njV2s53eWMwS1Rggkqrhg,10475
|
|
435
|
+
vibego-0.2.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
436
|
+
vibego-0.2.13.dist-info/entry_points.txt,sha256=Lsy_zm-dlyxt8-9DL9blBReIwU2k22c8-kifr46ND1M,48
|
|
437
|
+
vibego-0.2.13.dist-info/top_level.txt,sha256=R56CT3nW5H5v3ce0l3QDN4-C4qxTrNWzRTwrxnkDX4U,69
|
|
438
|
+
vibego-0.2.13.dist-info/RECORD,,
|
vibego_cli/__init__.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|