import os, sys, threading, asyncio, time, re, json import queue as Q PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, PROJECT_ROOT); os.chdir(PROJECT_ROOT) import lark_oapi as lark from lark_oapi.api.im.v1 import * from lark_oapi.api.contact.v3 import * from agentmain import GeneraticAgent import mykey _TAG_PATS = [r'<' + t + r'>.*?' for t in ('thinking', 'summary', 'tool_use', 'file_content')] def _clean(t): for p in _TAG_PATS: t = re.sub(p, '', t, flags=re.DOTALL) return re.sub(r'\n{3,}', '\n\n', t).strip() or '...' APP_ID, APP_SECRET = getattr(mykey, 'fs_app_id', None), getattr(mykey, 'fs_app_secret', None) ALLOWED_USERS = set(getattr(mykey, 'fs_allowed_users', [])) agent = GeneraticAgent() threading.Thread(target=agent.run, daemon=True).start() client, user_tasks = None, {} def create_client(): return lark.Client.builder().app_id(APP_ID).app_secret(APP_SECRET).log_level(lark.LogLevel.INFO).build() _card = lambda t: json.dumps({"config": {"wide_screen_mode": True}, "elements": [{"tag": "markdown", "content": t}]}) def send_message(open_id, content, msg_type="text", use_card=False): ct, mt = (_card(content), "interactive") if use_card else (json.dumps({"text": content}), "text") body = CreateMessageRequest.builder().receive_id_type("open_id").request_body( CreateMessageRequestBody.builder().receive_id(open_id).msg_type(mt).content(ct).build()).build() r = client.im.v1.message.create(body) return r.data.message_id if r.success() else (print(f"发送失败: {r.code}, {r.msg}"), None)[1] def update_message(message_id, content): body = PatchMessageRequest.builder().message_id(message_id).request_body( PatchMessageRequestBody.builder().content(_card(content)).build()).build() r = client.im.v1.message.patch(body) if not r.success(): print(f"[ERROR] update_message 失败: {r.code}, {r.msg}") return r.success() def handle_message(data): event, message, sender = data.event, data.event.message, data.event.sender open_id, msg_type = sender.sender_id.open_id, message.message_type if ALLOWED_USERS and open_id not in ALLOWED_USERS: return print(f"未授权用户: {open_id}") if msg_type != "text": return send_message(open_id, "⚠️ 目前只支持文本消息") text = json.loads(message.content).get("text", "").strip() if not text: return print(f"收到消息 [{open_id}]: {text}") if text.startswith("/"): return handle_command(open_id, text) def run_agent(): user_tasks[open_id] = {'running': True} try: msg_id, dq, last_text = send_message(open_id, "思考中...", use_card=True), agent.put_task(text, source='feishu'), "" while user_tasks.get(open_id, {}).get('running', False): time.sleep(3) item = None try: while True: item = dq.get_nowait() except: pass if item is None: continue raw, done = item.get("done") or item.get("next", ""), "done" in item show = _clean(raw) if len(show) > 3500: # 智能截断:避免切断代码块 cut = show[-3000:] if cut.count('```') % 2 == 1: cut = '```\n' + cut # 补开头 msg_id, last_text, show = send_message(open_id, "(继续...)", use_card=True), "", cut display = show if done else show + " ⏳" if display != last_text and msg_id: update_message(msg_id, display); last_text = display if done: break if not user_tasks.get(open_id, {}).get('running', True): send_message(open_id, "⏹️ 已停止") except Exception as e: import traceback; print(f"[ERROR] run_agent 异常: {e}"); traceback.print_exc() send_message(open_id, f"❌ 错误: {str(e)}") finally: user_tasks.pop(open_id, None) threading.Thread(target=run_agent, daemon=True).start() def handle_command(open_id, cmd): import glob if cmd == "/stop": if open_id in user_tasks: user_tasks[open_id]['running'] = False agent.abort(); send_message(open_id, "⏹️ 正在停止...") elif cmd == "/help": send_message(open_id, "📖 命令列表:\n/stop - 停止当前任务\n/status - 查看状态\n/restore - 恢复上次对话历史\n/new - 开启新对话\n/help - 显示帮助") elif cmd == "/status": send_message(open_id, f"状态: {'🟢 空闲' if not agent.is_busy() else '🔴 运行中'}") elif cmd == "/restore": try: files = glob.glob('./temp/model_responses_*.txt') if not files: return send_message(open_id, "❌ 没有找到历史记录") latest = max(files, key=os.path.getmtime) with open(latest, 'r', encoding='utf-8') as f: content = f.read() users = re.findall(r'=== USER ===\n(.+?)(?==== |$)', content, re.DOTALL) resps = re.findall(r'=== Response ===.*?\n(.+?)(?==== Prompt|$)', content, re.DOTALL) count = 0 for u, r in zip(users, resps): u, r = u.strip(), r.strip()[:500] if u and r: agent.history.extend([f"[USER]: {u}", f"[Agent] {r}"]); count += 1 agent.abort() send_message(open_id, f"✅ 已恢复 {count} 轮对话\n来源: {os.path.basename(latest)}\n(仅恢复上下文,请输入新问题继续)") except Exception as e: send_message(open_id, f"❌ 恢复失败: {e}") else: send_message(open_id, f"❓ 未知命令: {cmd}") def main(): global client if not APP_ID or not APP_SECRET: print("错误: 请在 mykey.py 中配置 fs_app_id 和 fs_app_secret"); sys.exit(1) client = create_client() handler = lark.EventDispatcherHandler.builder("", "").register_p2_im_message_receive_v1(handle_message).build() cli = lark.ws.Client(APP_ID, APP_SECRET, event_handler=handler, log_level=lark.LogLevel.INFO) print("=" * 50 + "\n飞书 Agent 已启动(长连接模式)\n" + f"App ID: {APP_ID}\n等待消息...\n" + "=" * 50) cli.start() if __name__ == "__main__": main()