vibego 0.2.12__py3-none-any.whl → 0.2.14__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.
- bot.py +160 -19
- {vibego-0.2.12.dist-info → vibego-0.2.14.dist-info}/METADATA +1 -1
- {vibego-0.2.12.dist-info → vibego-0.2.14.dist-info}/RECORD +7 -7
- vibego_cli/__init__.py +1 -1
- {vibego-0.2.12.dist-info → vibego-0.2.14.dist-info}/WHEEL +0 -0
- {vibego-0.2.12.dist-info → vibego-0.2.14.dist-info}/entry_points.txt +0 -0
- {vibego-0.2.12.dist-info → vibego-0.2.14.dist-info}/top_level.txt +0 -0
bot.py
CHANGED
|
@@ -125,7 +125,7 @@ AGENTS_PHASE_SUFFIX = ",最后列出当前所触发的 agents.md 的阶段、
|
|
|
125
125
|
VIBE_PHASE_PROMPT = f"进入vibe阶段{AGENTS_PHASE_SUFFIX}"
|
|
126
126
|
TEST_PHASE_PROMPT = f"进入测试阶段{AGENTS_PHASE_SUFFIX}"
|
|
127
127
|
|
|
128
|
-
_parse_mode_env = (os.environ.get("TELEGRAM_PARSE_MODE") or "
|
|
128
|
+
_parse_mode_env = (os.environ.get("TELEGRAM_PARSE_MODE") or "MarkdownV2").strip()
|
|
129
129
|
_parse_mode_key = _parse_mode_env.replace("-", "").replace("_", "").lower()
|
|
130
130
|
MODEL_OUTPUT_PARSE_MODE: Optional[ParseMode]
|
|
131
131
|
if _parse_mode_key in _PARSE_MODE_CANDIDATES:
|
|
@@ -753,7 +753,7 @@ async def _send_session_ack(
|
|
|
753
753
|
model_label = (ACTIVE_MODEL or "模型").strip() or "模型"
|
|
754
754
|
session_id = session_path.stem if session_path else "unknown"
|
|
755
755
|
prompt_message = (
|
|
756
|
-
f"{model_label}思考中,正在持续监听模型响应结果中。\n"
|
|
756
|
+
f"💭 {model_label}思考中,正在持续监听模型响应结果中。\n"
|
|
757
757
|
f"sessionId : {session_id}"
|
|
758
758
|
)
|
|
759
759
|
ack_message = await _reply_to_chat(
|
|
@@ -1203,6 +1203,118 @@ def _resolve_worker_target_chat_ids() -> List[int]:
|
|
|
1203
1203
|
return sorted(targets)
|
|
1204
1204
|
|
|
1205
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
|
+
|
|
1206
1318
|
async def _broadcast_worker_keyboard(bot: Bot) -> None:
|
|
1207
1319
|
"""启动时主动推送菜单,确保 Telegram 键盘同步。"""
|
|
1208
1320
|
targets = _resolve_worker_target_chat_ids()
|
|
@@ -2657,7 +2769,8 @@ def _build_summary_prompt(
|
|
|
2657
2769
|
) -> str:
|
|
2658
2770
|
"""构造模型摘要提示词,要求携带请求标识。"""
|
|
2659
2771
|
|
|
2660
|
-
|
|
2772
|
+
# 摘要提示词是发送给模型的,使用纯文本格式,不需要 Markdown 转义
|
|
2773
|
+
task_code = f"/{task.id}" if task.id else "-"
|
|
2661
2774
|
title = task.title or "-"
|
|
2662
2775
|
status_label = STATUS_LABELS.get(task.status, task.status)
|
|
2663
2776
|
note_lines: list[str] = []
|
|
@@ -2820,8 +2933,9 @@ def _build_task_history_view(
|
|
|
2820
2933
|
if total_items == 0:
|
|
2821
2934
|
raise ValueError("暂无事件记录")
|
|
2822
2935
|
|
|
2936
|
+
# 历史记录会被包裹在代码块中显示,使用纯文本格式,不需要 Markdown 转义
|
|
2823
2937
|
title_text = normalize_newlines(task.title or "").strip() or "-"
|
|
2824
|
-
title_display =
|
|
2938
|
+
title_display = title_text
|
|
2825
2939
|
|
|
2826
2940
|
digit_width = len(str(max(total_items, 1)))
|
|
2827
2941
|
placeholder_page = "9" * digit_width
|
|
@@ -3650,7 +3764,19 @@ def _get_delivered_offsets(chat_id: int, session_key: str) -> set[int]:
|
|
|
3650
3764
|
return CHAT_DELIVERED_OFFSETS.setdefault(chat_id, {}).setdefault(session_key, set())
|
|
3651
3765
|
|
|
3652
3766
|
|
|
3653
|
-
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
|
+
"""
|
|
3654
3780
|
session_key = str(session_path)
|
|
3655
3781
|
previous_offset = SESSION_OFFSETS.get(session_key, 0)
|
|
3656
3782
|
new_offset, events = _read_session_events(session_path)
|
|
@@ -3725,7 +3851,8 @@ async def _deliver_pending_messages(chat_id: int, session_path: Path) -> bool:
|
|
|
3725
3851
|
last_committed_offset = event_offset
|
|
3726
3852
|
SESSION_OFFSETS[session_key] = event_offset
|
|
3727
3853
|
continue
|
|
3728
|
-
|
|
3854
|
+
# 根据轮询阶段决定是否添加完成前缀
|
|
3855
|
+
formatted_text = _prepend_completion_header(text_to_send) if add_completion_header else text_to_send
|
|
3729
3856
|
payload_for_hash = _prepare_model_payload(formatted_text)
|
|
3730
3857
|
initial_hash = hashlib.sha256(payload_for_hash.encode("utf-8", errors="ignore")).hexdigest()
|
|
3731
3858
|
if initial_hash in delivered_hashes:
|
|
@@ -3836,22 +3963,24 @@ async def _deliver_pending_messages(chat_id: int, session_path: Path) -> bool:
|
|
|
3836
3963
|
SESSION_OFFSETS[session_key] = max(last_committed_offset, new_offset)
|
|
3837
3964
|
|
|
3838
3965
|
if delivered_response:
|
|
3966
|
+
# 实际发送了消息,返回 True 表示本次调用成功发送
|
|
3967
|
+
# 这样可以确保延迟轮询机制被正确触发
|
|
3839
3968
|
if ENABLE_PLAN_PROGRESS and plan_active:
|
|
3840
3969
|
worker_log.info(
|
|
3841
|
-
"
|
|
3970
|
+
"模型输出已发送,但计划仍在更新",
|
|
3971
|
+
extra={
|
|
3972
|
+
**_session_extra(path=session_path),
|
|
3973
|
+
"chat": chat_id,
|
|
3974
|
+
},
|
|
3975
|
+
)
|
|
3976
|
+
else:
|
|
3977
|
+
worker_log.info(
|
|
3978
|
+
"模型输出已发送且计划完成",
|
|
3842
3979
|
extra={
|
|
3843
3980
|
**_session_extra(path=session_path),
|
|
3844
3981
|
"chat": chat_id,
|
|
3845
3982
|
},
|
|
3846
3983
|
)
|
|
3847
|
-
return False
|
|
3848
|
-
worker_log.info(
|
|
3849
|
-
"模型输出已发送且计划完成",
|
|
3850
|
-
extra={
|
|
3851
|
-
**_session_extra(path=session_path),
|
|
3852
|
-
"chat": chat_id,
|
|
3853
|
-
},
|
|
3854
|
-
)
|
|
3855
3984
|
return True
|
|
3856
3985
|
|
|
3857
3986
|
if ENABLE_PLAN_PROGRESS and not plan_active and final_response_sent:
|
|
@@ -4190,16 +4319,17 @@ async def _watch_and_notify(chat_id: int, session_path: Path,
|
|
|
4190
4319
|
|
|
4191
4320
|
两阶段轮询机制:
|
|
4192
4321
|
- 阶段1(快速轮询):interval 间隔(通常 0.3 秒),直到首次发送成功
|
|
4193
|
-
- 阶段2(延迟轮询):
|
|
4322
|
+
- 阶段2(延迟轮询):3 秒间隔,最多 600 次(持续 30 分钟),捕获长时间任务的后续输出
|
|
4194
4323
|
|
|
4195
4324
|
异常安全:使用 try...finally 确保状态清理。
|
|
4325
|
+
中断机制:收到新 Telegram 消息时会设置 interrupted 标志,轮询自动停止。
|
|
4196
4326
|
"""
|
|
4197
4327
|
start = time.monotonic()
|
|
4198
4328
|
first_delivery_done = False
|
|
4199
4329
|
current_interval = interval # 初始为快速轮询间隔(0.3 秒)
|
|
4200
4330
|
long_poll_rounds = 0
|
|
4201
|
-
long_poll_max_rounds =
|
|
4202
|
-
long_poll_interval =
|
|
4331
|
+
long_poll_max_rounds = 600 # 30 分钟 / 3 秒 = 600 次
|
|
4332
|
+
long_poll_interval = 3.0 # 3 秒
|
|
4203
4333
|
|
|
4204
4334
|
try:
|
|
4205
4335
|
while True:
|
|
@@ -4233,7 +4363,12 @@ async def _watch_and_notify(chat_id: int, session_path: Path,
|
|
|
4233
4363
|
continue
|
|
4234
4364
|
|
|
4235
4365
|
try:
|
|
4236
|
-
|
|
4366
|
+
# 快速轮询阶段添加前缀,延迟轮询阶段不添加
|
|
4367
|
+
delivered = await _deliver_pending_messages(
|
|
4368
|
+
chat_id,
|
|
4369
|
+
session_path,
|
|
4370
|
+
add_completion_header=not first_delivery_done
|
|
4371
|
+
)
|
|
4237
4372
|
except Exception as exc:
|
|
4238
4373
|
worker_log.error(
|
|
4239
4374
|
"消息发送时发生未预期异常",
|
|
@@ -6918,6 +7053,9 @@ async def on_edit_new_value(message: Message, state: FSMContext) -> None:
|
|
|
6918
7053
|
|
|
6919
7054
|
@router.message(CommandStart())
|
|
6920
7055
|
async def on_start(m: Message):
|
|
7056
|
+
# 首次收到消息时自动记录 chat_id 到 state 文件
|
|
7057
|
+
_auto_record_chat_id(m.chat.id)
|
|
7058
|
+
|
|
6921
7059
|
await m.answer(
|
|
6922
7060
|
(
|
|
6923
7061
|
f"Hello, {m.from_user.full_name}!\n"
|
|
@@ -6933,6 +7071,9 @@ async def on_start(m: Message):
|
|
|
6933
7071
|
|
|
6934
7072
|
@router.message(F.text)
|
|
6935
7073
|
async def on_text(m: Message):
|
|
7074
|
+
# 首次收到消息时自动记录 chat_id 到 state 文件
|
|
7075
|
+
_auto_record_chat_id(m.chat.id)
|
|
7076
|
+
|
|
6936
7077
|
prompt = (m.text or "").strip()
|
|
6937
7078
|
if not prompt:
|
|
6938
7079
|
return await m.answer("请输入非空提示词")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
bot.py,sha256=
|
|
1
|
+
bot.py,sha256=2JR3qc1RHBrO9TtTnDoqxf1cXR9Lt9JRfXlP32gAFkE,262378
|
|
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
|
|
@@ -425,14 +425,14 @@ tasks/constants.py,sha256=tS1kZxBIUm3JJUMHm25XI-KHNUZl5NhbbuzjzL_rF-c,299
|
|
|
425
425
|
tasks/fsm.py,sha256=rKXXLEieQQU4r2z_CZUvn1_70FXiZXBBugF40gpe_tQ,1476
|
|
426
426
|
tasks/models.py,sha256=N_qqRBo9xMSV0vbn4k6bLBXT8C_dp_oTFUxvdx16ZQM,2459
|
|
427
427
|
tasks/service.py,sha256=w_S_aWiVqRXzXEpimLDsuCCCX2lB5uDkff9aKThBw9c,41916
|
|
428
|
-
vibego_cli/__init__.py,sha256=
|
|
428
|
+
vibego_cli/__init__.py,sha256=gPVIUj15YEjG1hu3jHKkCr2Qnqy4AVAVF1OVFWnFJs4,311
|
|
429
429
|
vibego_cli/__main__.py,sha256=qqTrYmRRLe4361fMzbI3-CqpZ7AhTofIHmfp4ykrrBY,158
|
|
430
430
|
vibego_cli/config.py,sha256=33WSORCfUIxrDtgASPEbVqVLBVNHh-RSFLpNy7tfc0s,2992
|
|
431
431
|
vibego_cli/deps.py,sha256=1nRXI7Dd-S1hYE8DligzK5fIluQWETRUj4_OKL0DikQ,1419
|
|
432
432
|
vibego_cli/main.py,sha256=e2W5Pb9U9rfmF-jNX9uIA3222lhM0GgcvSdFTDBZd2s,12086
|
|
433
433
|
vibego_cli/data/worker_requirements.txt,sha256=QSt30DSSSHtfucTFPpc7twk9kLS5rVLNTcvDiagxrZg,62
|
|
434
|
-
vibego-0.2.
|
|
435
|
-
vibego-0.2.
|
|
436
|
-
vibego-0.2.
|
|
437
|
-
vibego-0.2.
|
|
438
|
-
vibego-0.2.
|
|
434
|
+
vibego-0.2.14.dist-info/METADATA,sha256=lMNiS2blR-0f1ATbqj03vZr8BBAgrKEeEqngBaeYctk,10475
|
|
435
|
+
vibego-0.2.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
436
|
+
vibego-0.2.14.dist-info/entry_points.txt,sha256=Lsy_zm-dlyxt8-9DL9blBReIwU2k22c8-kifr46ND1M,48
|
|
437
|
+
vibego-0.2.14.dist-info/top_level.txt,sha256=R56CT3nW5H5v3ce0l3QDN4-C4qxTrNWzRTwrxnkDX4U,69
|
|
438
|
+
vibego-0.2.14.dist-info/RECORD,,
|
vibego_cli/__init__.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|