vibego 0.2.11__py3-none-any.whl → 0.2.12__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 +193 -22
- 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.12.dist-info}/METADATA +1 -1
- {vibego-0.2.11.dist-info → vibego-0.2.12.dist-info}/RECORD +12 -11
- vibego_cli/__init__.py +1 -1
- {vibego-0.2.11.dist-info → vibego-0.2.12.dist-info}/WHEEL +0 -0
- {vibego-0.2.11.dist-info → vibego-0.2.12.dist-info}/entry_points.txt +0 -0
- {vibego-0.2.11.dist-info → vibego-0.2.12.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,
|
|
@@ -3050,6 +3053,9 @@ CHAT_DELIVERED_HASHES: Dict[int, Dict[str, set[str]]] = {}
|
|
|
3050
3053
|
CHAT_DELIVERED_OFFSETS: Dict[int, Dict[str, set[int]]] = {}
|
|
3051
3054
|
CHAT_REPLY_COUNT: Dict[int, Dict[str, int]] = {}
|
|
3052
3055
|
CHAT_COMPACT_STATE: Dict[int, Dict[str, Dict[str, Any]]] = {}
|
|
3056
|
+
# 长轮询状态:用于延迟轮询机制
|
|
3057
|
+
CHAT_LONG_POLL_STATE: Dict[int, Dict[str, Any]] = {}
|
|
3058
|
+
CHAT_LONG_POLL_LOCK: Optional[asyncio.Lock] = None # 在事件循环启动后初始化
|
|
3053
3059
|
SUMMARY_REQUEST_TIMEOUT_SECONDS = 300.0
|
|
3054
3060
|
|
|
3055
3061
|
|
|
@@ -3992,6 +3998,9 @@ async def _ensure_session_watcher(chat_id: int) -> Optional[Path]:
|
|
|
3992
3998
|
if watcher is not None and watcher.done():
|
|
3993
3999
|
CHAT_WATCHERS.pop(chat_id, None)
|
|
3994
4000
|
|
|
4001
|
+
# 中断旧的延迟轮询(如果存在)
|
|
4002
|
+
await _interrupt_long_poll(chat_id)
|
|
4003
|
+
|
|
3995
4004
|
CHAT_WATCHERS[chat_id] = asyncio.create_task(
|
|
3996
4005
|
_watch_and_notify(
|
|
3997
4006
|
chat_id,
|
|
@@ -4151,23 +4160,175 @@ async def _finalize_plan_progress(chat_id: int) -> None:
|
|
|
4151
4160
|
|
|
4152
4161
|
|
|
4153
4162
|
|
|
4163
|
+
async def _interrupt_long_poll(chat_id: int) -> None:
|
|
4164
|
+
"""
|
|
4165
|
+
中断指定 chat 的延迟轮询。
|
|
4166
|
+
|
|
4167
|
+
当用户发送新消息时调用,确保旧的延迟轮询被终止,
|
|
4168
|
+
为新的监听任务让路。
|
|
4169
|
+
|
|
4170
|
+
线程安全:使用 asyncio.Lock 保护状态访问。
|
|
4171
|
+
"""
|
|
4172
|
+
if CHAT_LONG_POLL_LOCK is None:
|
|
4173
|
+
# 锁未初始化(测试环境或启动早期)
|
|
4174
|
+
return
|
|
4175
|
+
|
|
4176
|
+
async with CHAT_LONG_POLL_LOCK:
|
|
4177
|
+
state = CHAT_LONG_POLL_STATE.get(chat_id)
|
|
4178
|
+
if state is not None:
|
|
4179
|
+
state["interrupted"] = True
|
|
4180
|
+
worker_log.info(
|
|
4181
|
+
"标记延迟轮询为待中断",
|
|
4182
|
+
extra={"chat": chat_id},
|
|
4183
|
+
)
|
|
4184
|
+
|
|
4185
|
+
|
|
4154
4186
|
async def _watch_and_notify(chat_id: int, session_path: Path,
|
|
4155
4187
|
max_wait: float, interval: float):
|
|
4188
|
+
"""
|
|
4189
|
+
监听会话文件并发送消息。
|
|
4190
|
+
|
|
4191
|
+
两阶段轮询机制:
|
|
4192
|
+
- 阶段1(快速轮询):interval 间隔(通常 0.3 秒),直到首次发送成功
|
|
4193
|
+
- 阶段2(延迟轮询):180 秒间隔,最多 10 次,捕获长时间任务的后续输出
|
|
4194
|
+
|
|
4195
|
+
异常安全:使用 try...finally 确保状态清理。
|
|
4196
|
+
"""
|
|
4156
4197
|
start = time.monotonic()
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4198
|
+
first_delivery_done = False
|
|
4199
|
+
current_interval = interval # 初始为快速轮询间隔(0.3 秒)
|
|
4200
|
+
long_poll_rounds = 0
|
|
4201
|
+
long_poll_max_rounds = 10
|
|
4202
|
+
long_poll_interval = 180.0 # 3 分钟
|
|
4203
|
+
|
|
4204
|
+
try:
|
|
4205
|
+
while True:
|
|
4206
|
+
# 检查是否被新消息中断(使用锁保护)
|
|
4207
|
+
if CHAT_LONG_POLL_LOCK is not None:
|
|
4208
|
+
async with CHAT_LONG_POLL_LOCK:
|
|
4209
|
+
state = CHAT_LONG_POLL_STATE.get(chat_id)
|
|
4210
|
+
if state is not None and state.get("interrupted", False):
|
|
4211
|
+
worker_log.info(
|
|
4212
|
+
"延迟轮询被新消息中断",
|
|
4213
|
+
extra={
|
|
4214
|
+
**_session_extra(path=session_path),
|
|
4215
|
+
"chat": chat_id,
|
|
4216
|
+
"round": long_poll_rounds,
|
|
4217
|
+
},
|
|
4218
|
+
)
|
|
4219
|
+
return
|
|
4220
|
+
|
|
4221
|
+
await asyncio.sleep(current_interval)
|
|
4222
|
+
|
|
4223
|
+
# 检查超时(仅在快速轮询阶段)
|
|
4224
|
+
if not first_delivery_done and max_wait > 0 and time.monotonic() - start > max_wait:
|
|
4225
|
+
worker_log.warning(
|
|
4226
|
+
"[session-map] chat=%s 长时间未获取到 Codex 输出,停止轮询",
|
|
4227
|
+
chat_id,
|
|
4228
|
+
extra=_session_extra(path=session_path),
|
|
4229
|
+
)
|
|
4230
|
+
return
|
|
4231
|
+
|
|
4232
|
+
if not session_path.exists():
|
|
4233
|
+
continue
|
|
4234
|
+
|
|
4235
|
+
try:
|
|
4236
|
+
delivered = await _deliver_pending_messages(chat_id, session_path)
|
|
4237
|
+
except Exception as exc:
|
|
4238
|
+
worker_log.error(
|
|
4239
|
+
"消息发送时发生未预期异常",
|
|
4240
|
+
exc_info=exc,
|
|
4241
|
+
extra={
|
|
4242
|
+
**_session_extra(path=session_path),
|
|
4243
|
+
"chat": chat_id,
|
|
4244
|
+
},
|
|
4245
|
+
)
|
|
4246
|
+
delivered = False
|
|
4247
|
+
|
|
4248
|
+
# 首次发送成功,切换到延迟轮询模式
|
|
4249
|
+
if delivered and not first_delivery_done:
|
|
4250
|
+
first_delivery_done = True
|
|
4251
|
+
current_interval = long_poll_interval
|
|
4252
|
+
if CHAT_LONG_POLL_LOCK is not None:
|
|
4253
|
+
async with CHAT_LONG_POLL_LOCK:
|
|
4254
|
+
CHAT_LONG_POLL_STATE[chat_id] = {
|
|
4255
|
+
"active": True,
|
|
4256
|
+
"round": 0,
|
|
4257
|
+
"max_rounds": long_poll_max_rounds,
|
|
4258
|
+
"interrupted": False,
|
|
4259
|
+
}
|
|
4260
|
+
worker_log.info(
|
|
4261
|
+
"首次发送成功,启动延迟轮询模式",
|
|
4262
|
+
extra={
|
|
4263
|
+
**_session_extra(path=session_path),
|
|
4264
|
+
"chat": chat_id,
|
|
4265
|
+
"interval": long_poll_interval,
|
|
4266
|
+
"max_rounds": long_poll_max_rounds,
|
|
4267
|
+
},
|
|
4268
|
+
)
|
|
4269
|
+
continue
|
|
4270
|
+
|
|
4271
|
+
# 延迟轮询阶段
|
|
4272
|
+
if first_delivery_done:
|
|
4273
|
+
if delivered:
|
|
4274
|
+
# 又收到新消息,重置轮询计数
|
|
4275
|
+
long_poll_rounds = 0
|
|
4276
|
+
if CHAT_LONG_POLL_LOCK is not None:
|
|
4277
|
+
async with CHAT_LONG_POLL_LOCK:
|
|
4278
|
+
state = CHAT_LONG_POLL_STATE.get(chat_id)
|
|
4279
|
+
if state is not None:
|
|
4280
|
+
state["round"] = 0
|
|
4281
|
+
worker_log.info(
|
|
4282
|
+
"延迟轮询中收到新消息,重置计数",
|
|
4283
|
+
extra={
|
|
4284
|
+
**_session_extra(path=session_path),
|
|
4285
|
+
"chat": chat_id,
|
|
4286
|
+
},
|
|
4287
|
+
)
|
|
4288
|
+
else:
|
|
4289
|
+
# 无新消息,增加轮询计数
|
|
4290
|
+
long_poll_rounds += 1
|
|
4291
|
+
if CHAT_LONG_POLL_LOCK is not None:
|
|
4292
|
+
async with CHAT_LONG_POLL_LOCK:
|
|
4293
|
+
state = CHAT_LONG_POLL_STATE.get(chat_id)
|
|
4294
|
+
if state is not None:
|
|
4295
|
+
state["round"] = long_poll_rounds
|
|
4296
|
+
|
|
4297
|
+
if long_poll_rounds >= long_poll_max_rounds:
|
|
4298
|
+
worker_log.info(
|
|
4299
|
+
"延迟轮询达到最大次数,停止监听",
|
|
4300
|
+
extra={
|
|
4301
|
+
**_session_extra(path=session_path),
|
|
4302
|
+
"chat": chat_id,
|
|
4303
|
+
"total_rounds": long_poll_rounds,
|
|
4304
|
+
},
|
|
4305
|
+
)
|
|
4306
|
+
return
|
|
4307
|
+
|
|
4308
|
+
worker_log.debug(
|
|
4309
|
+
"延迟轮询中无新消息",
|
|
4310
|
+
extra={
|
|
4311
|
+
**_session_extra(path=session_path),
|
|
4312
|
+
"chat": chat_id,
|
|
4313
|
+
"round": f"{long_poll_rounds}/{long_poll_max_rounds}",
|
|
4314
|
+
},
|
|
4315
|
+
)
|
|
4316
|
+
continue
|
|
4317
|
+
|
|
4318
|
+
# 快速轮询阶段:如果已发送消息,退出
|
|
4319
|
+
if delivered:
|
|
4320
|
+
return
|
|
4321
|
+
|
|
4322
|
+
finally:
|
|
4323
|
+
# 确保无论如何都清理延迟轮询状态
|
|
4324
|
+
if CHAT_LONG_POLL_LOCK is not None:
|
|
4325
|
+
async with CHAT_LONG_POLL_LOCK:
|
|
4326
|
+
if chat_id in CHAT_LONG_POLL_STATE:
|
|
4327
|
+
CHAT_LONG_POLL_STATE.pop(chat_id, None)
|
|
4328
|
+
worker_log.debug(
|
|
4329
|
+
"监听任务退出,已清理延迟轮询状态",
|
|
4330
|
+
extra={"chat": chat_id},
|
|
4331
|
+
)
|
|
4171
4332
|
|
|
4172
4333
|
|
|
4173
4334
|
def _read_pointer_path(pointer: Path) -> Optional[Path]:
|
|
@@ -5422,7 +5583,7 @@ async def on_task_desc_input(message: Message, state: FSMContext) -> None:
|
|
|
5422
5583
|
|
|
5423
5584
|
@router.message(TaskDescriptionStates.waiting_confirm)
|
|
5424
5585
|
async def on_task_desc_confirm_stage_text(message: Message, state: FSMContext) -> None:
|
|
5425
|
-
"""
|
|
5586
|
+
"""处理任务描述确认阶段的菜单指令。支持按钮点击、数字编号和直接文本输入。"""
|
|
5426
5587
|
|
|
5427
5588
|
data = await state.get_data()
|
|
5428
5589
|
task_id = data.get("task_id")
|
|
@@ -5431,13 +5592,19 @@ async def on_task_desc_confirm_stage_text(message: Message, state: FSMContext) -
|
|
|
5431
5592
|
await message.answer("会话已失效,请重新操作。", reply_markup=_build_worker_main_keyboard())
|
|
5432
5593
|
return
|
|
5433
5594
|
|
|
5434
|
-
|
|
5435
|
-
|
|
5595
|
+
# 使用 _resolve_reply_choice() 智能解析用户输入,支持数字编号、按钮文本和直接文本
|
|
5596
|
+
options = [TASK_DESC_CONFIRM_TEXT, TASK_DESC_RETRY_TEXT, TASK_DESC_CANCEL_TEXT]
|
|
5597
|
+
resolved = _resolve_reply_choice(message.text, options=options)
|
|
5598
|
+
stripped = _strip_number_prefix((message.text or "").strip()).lower()
|
|
5599
|
+
|
|
5600
|
+
# 处理取消操作
|
|
5601
|
+
if resolved == options[2] or _is_cancel_message(resolved) or stripped in {"取消"}:
|
|
5436
5602
|
await state.clear()
|
|
5437
5603
|
await message.answer("已取消编辑任务描述。", reply_markup=_build_worker_main_keyboard())
|
|
5438
5604
|
return
|
|
5439
5605
|
|
|
5440
|
-
|
|
5606
|
+
# 处理重新输入操作
|
|
5607
|
+
if resolved == options[1] or stripped in {"重新输入"}:
|
|
5441
5608
|
task = await TASK_SERVICE.get_task(task_id)
|
|
5442
5609
|
if task is None:
|
|
5443
5610
|
await state.clear()
|
|
@@ -5455,7 +5622,8 @@ async def on_task_desc_confirm_stage_text(message: Message, state: FSMContext) -
|
|
|
5455
5622
|
)
|
|
5456
5623
|
return
|
|
5457
5624
|
|
|
5458
|
-
|
|
5625
|
+
# 处理确认更新操作
|
|
5626
|
+
if resolved == options[0] or stripped in {"确认", "确认更新"}:
|
|
5459
5627
|
new_description = data.get("new_description")
|
|
5460
5628
|
if new_description is None:
|
|
5461
5629
|
await state.set_state(TaskDescriptionStates.waiting_content)
|
|
@@ -5486,8 +5654,9 @@ async def on_task_desc_confirm_stage_text(message: Message, state: FSMContext) -
|
|
|
5486
5654
|
)
|
|
5487
5655
|
return
|
|
5488
5656
|
|
|
5657
|
+
# 无效输入,提示用户
|
|
5489
5658
|
await message.answer(
|
|
5490
|
-
"
|
|
5659
|
+
"当前处于确认阶段,请选择确认、重新输入或取消,可直接输入编号或点击键盘按钮:",
|
|
5491
5660
|
reply_markup=_build_task_desc_confirm_keyboard(),
|
|
5492
5661
|
)
|
|
5493
5662
|
|
|
@@ -6869,7 +7038,9 @@ async def _ensure_worker_menu_button(bot: Bot) -> None:
|
|
|
6869
7038
|
)
|
|
6870
7039
|
|
|
6871
7040
|
async def main():
|
|
6872
|
-
global _bot
|
|
7041
|
+
global _bot, CHAT_LONG_POLL_LOCK
|
|
7042
|
+
# 初始化长轮询锁
|
|
7043
|
+
CHAT_LONG_POLL_LOCK = asyncio.Lock()
|
|
6873
7044
|
_bot = build_bot()
|
|
6874
7045
|
try:
|
|
6875
7046
|
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=NPa3-Zb_20O8AjpQTUBDFfTZesh5kVEXRK17SbNpu_0,256916
|
|
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=qgEUIxQ50ynuK_Dcun8OGqFZe0ICJWheDr1_-6tT9bU,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.12.dist-info/METADATA,sha256=rqZTvJDLdYpOe_2FY4rEvGfnVVWC543yh0Nn944vCt8,10475
|
|
435
|
+
vibego-0.2.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
436
|
+
vibego-0.2.12.dist-info/entry_points.txt,sha256=Lsy_zm-dlyxt8-9DL9blBReIwU2k22c8-kifr46ND1M,48
|
|
437
|
+
vibego-0.2.12.dist-info/top_level.txt,sha256=R56CT3nW5H5v3ce0l3QDN4-C4qxTrNWzRTwrxnkDX4U,69
|
|
438
|
+
vibego-0.2.12.dist-info/RECORD,,
|
vibego_cli/__init__.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|