diff --git a/.gitignore b/.gitignore index 70945cb..8fe55e6 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,4 @@ restore_commit.txt sche_tasks/ QUICK_START.md +**log.* \ No newline at end of file diff --git a/agentmain.py b/agentmain.py index f4a6291..226bc2d 100644 --- a/agentmain.py +++ b/agentmain.py @@ -9,25 +9,30 @@ from sidercall import SiderLLMSession, LLMSession, ToolClient, ClaudeSession, Xa from agent_loop import agent_runner_loop, StepOutcome, BaseHandler from ga import GenericAgentHandler, smart_format, get_global_memory, format_error -with open('assets/tools_schema.json', 'r', encoding='utf-8') as f: +script_dir = os.path.dirname(os.path.abspath(__file__)) +with open(os.path.join(script_dir, 'assets/tools_schema.json'), 'r', encoding='utf-8') as f: TS = f.read() TOOLS_SCHEMA = json.loads(TS if os.name == 'nt' else TS.replace('powershell', 'bash')) def get_system_prompt(): - if not os.path.exists('memory'): os.makedirs('memory') - if not os.path.exists('memory/global_mem.txt'): - with open('memory/global_mem.txt', 'w', encoding='utf-8') as f: f.write('') - if not os.path.exists('memory/global_mem_insight.txt'): - t = 'assets/global_mem_insight_template.txt' - open('memory/global_mem_insight.txt', 'w', encoding='utf-8').write(open(t, encoding='utf-8').read() if os.path.exists(t) else '') - with open('assets/sys_prompt.txt', 'r', encoding='utf-8') as f: prompt = f.read() + script_dir = os.path.dirname(os.path.abspath(__file__)) + memory_dir = os.path.join(script_dir, 'memory') + if not os.path.exists(memory_dir): os.makedirs(memory_dir) + if not os.path.exists(os.path.join(memory_dir, 'global_mem.txt')): + with open(os.path.join(memory_dir, 'global_mem.txt'), 'w', encoding='utf-8') as f: f.write('') + if not os.path.exists(os.path.join(memory_dir, 'global_mem_insight.txt')): + t = os.path.join(script_dir, 'assets/global_mem_insight_template.txt') + open(os.path.join(memory_dir, 'global_mem_insight.txt'), 'w', encoding='utf-8').write(open(t, encoding='utf-8').read() if os.path.exists(t) else '') + with open(os.path.join(script_dir, 'assets/sys_prompt.txt'), 'r', encoding='utf-8') as f: prompt = f.read() prompt += f"\nToday: {time.strftime('%Y-%m-%d %a')}\n" prompt += get_global_memory() return prompt class GeneraticAgent: def __init__(self): - if not os.path.exists('temp'): os.makedirs('temp') + script_dir = os.path.dirname(os.path.abspath(__file__)) + temp_dir = os.path.join(script_dir, 'temp') + if not os.path.exists(temp_dir): os.makedirs(temp_dir) from sidercall import mykeys llm_sessions = [] for k, cfg in mykeys.items(): @@ -85,7 +90,8 @@ class GeneraticAgent: self.history.append(f"[USER]: {rquery}") sys_prompt = get_system_prompt() - handler = GenericAgentHandler(None, self.history, './temp') + script_dir = os.path.dirname(os.path.abspath(__file__)) + handler = GenericAgentHandler(None, self.history, os.path.join(script_dir, 'temp')) if self.handler and self.handler.key_info: handler.key_info = self.handler.key_info if '清除工作记忆' not in handler.key_info: @@ -132,8 +138,9 @@ if __name__ == '__main__': threading.Thread(target=agent.run, daemon=True).start() if args.task: - d = f'temp/{args.task}'; rp = f'{d}/reply.txt'; nround = '' - with open(f'{d}/input.txt', encoding='utf-8') as f: raw = f.read() + script_dir = os.path.dirname(os.path.abspath(__file__)) + d = os.path.join(script_dir, f'temp/{args.task}'); rp = os.path.join(d, 'reply.txt'); nround = '' + with open(os.path.join(d, 'input.txt'), encoding='utf-8') as f: raw = f.read() while True: dq = agent.put_task(raw, source='task') while 'done' not in (item := dq.get(timeout=120)): @@ -169,23 +176,27 @@ if __name__ == '__main__': except Exception as e: if once: raise print(f'[Reflect] drain error: {e}'); result = f'[ERROR] {e}' - open('./temp/reflect.log', 'a', encoding='utf-8').write(f'[{datetime.now():%m-%d %H:%M}]\n{result}\n\n') + script_dir = os.path.dirname(os.path.abspath(__file__)) + open(os.path.join(script_dir, './temp/reflect.log'), 'a', encoding='utf-8').write(f'[{datetime.now():%m-%d %H:%M}]\n{result}\n\n') if on_done: try: on_done(result) except Exception as e: print(f'[Reflect] on_done error: {e}') if once: print('[Reflect] ONCE=True, exiting.'); break elif args.scheduled: + script_dir = os.path.dirname(os.path.abspath(__file__)) def drain(dq, tag): while 'done' not in (item := dq.get()): pass - open('./temp/scheduler.log', 'a', encoding='utf-8').write(f'[{datetime.now():%m-%d %H:%M}] {tag}\n{item["done"]}\n\n') + open(os.path.join(script_dir, './temp/scheduler.log'), 'a', encoding='utf-8').write(f'[{datetime.now():%m-%d %H:%M}] {tag}\n{item["done"]}\n\n') while True: time.sleep(55 + random.random() * 10) now = datetime.now() - if not os.path.isdir('./sche_tasks/pending'): continue - for f in os.listdir('./sche_tasks/pending'): + script_dir = os.path.dirname(os.path.abspath(__file__)) + sche_tasks_dir = os.path.join(script_dir, './sche_tasks/pending') + if not os.path.isdir(sche_tasks_dir): continue + for f in os.listdir(sche_tasks_dir): m = re.match(r'(\d{4}-\d{2}-\d{2})_(\d{4})_', f) if m and now >= datetime.strptime(f'{m[1]} {m[2]}', '%Y-%m-%d %H%M'): - raw = open(f'./sche_tasks/pending/{f}', encoding='utf-8').read() + raw = open(os.path.join(sche_tasks_dir, f), encoding='utf-8').read() dq = agent.put_task(f'按scheduled_task_sop执行任务文件 ../sche_tasks/pending/{f}(立刻移到running)\n内容:\n{raw}', source='scheduler') threading.Thread(target=drain, args=(dq, f), daemon=True).start() break @@ -202,4 +213,4 @@ if __name__ == '__main__': if 'done' in item: print(); break except KeyboardInterrupt: agent.abort() - print('\n[Interrupted]') + print('\n[Interrupted]') \ No newline at end of file diff --git a/assets/make_prompts.py b/assets/make_prompts.py index 72b19e4..8669ce6 100644 --- a/assets/make_prompts.py +++ b/assets/make_prompts.py @@ -15,7 +15,8 @@ def generate_tool_schema(): """ 通过代码内省,将 Handler 的逻辑映射为高语义的工具描述。 """ - with open('../ga.py', 'r', encoding='utf-8') as f: + script_dir = os.path.dirname(os.path.abspath(__file__)) + with open(os.path.join(script_dir, '../ga.py'), 'r', encoding='utf-8') as f: ga_code = f.read() # 极简且具备高度概括能力的元 Prompt meta_prompt = f""" @@ -69,7 +70,8 @@ def generate_tool_schema(): final_schema = json.loads(clean_json) if final_schema: - with open('tools_schema.json', 'w', encoding='utf-8') as f: + script_dir = os.path.dirname(os.path.abspath(__file__)) + with open(os.path.join(script_dir, 'tools_schema.json'), 'w', encoding='utf-8') as f: json.dump(final_schema, f, indent=2, ensure_ascii=False) print("✅ 成功从代码内省生成 Schema 并持久化。") return final_schema @@ -79,7 +81,10 @@ def generate_tool_schema(): return None -def make_system_prompt(ga_code_path='../ga.py'): +def make_system_prompt(ga_code_path=None): + script_dir = os.path.dirname(os.path.abspath(__file__)) + if ga_code_path is None: + ga_code_path = os.path.join(script_dir, '../ga.py') with open(ga_code_path, 'r', encoding='utf-8') as f: ga_code = f.read() @@ -117,7 +122,7 @@ def make_system_prompt(ga_code_path='../ga.py'): print("📝 生成的 System Prompt 内容如下:\n") print(system_prompt_content) clean_content = re.sub(r'<[^>]+>', '', system_prompt_content) - with open('sys_prompt.txt', 'w', encoding='utf-8') as f: + with open(os.path.join(script_dir, 'sys_prompt.txt'), 'w', encoding='utf-8') as f: f.write(clean_content) return clean_content diff --git a/ga.py b/ga.py index 930bc00..8cfe4fd 100644 --- a/ga.py +++ b/ga.py @@ -16,7 +16,8 @@ def code_run(code, code_type="python", timeout=60, cwd=None, code_cwd=None, stop """ preview = (code[:60].replace('\n', ' ') + '...') if len(code) > 60 else code.strip() yield f"[Action] Running {code_type} in {os.path.basename(cwd)}: {preview}\n" - cwd = cwd or os.path.join(os.getcwd(), 'temp'); tmp_path = None + script_dir = os.path.dirname(os.path.abspath(__file__)) + cwd = cwd or os.path.join(script_dir, 'temp'); tmp_path = None if code_type == "python": tmp_file = tempfile.NamedTemporaryFile(suffix=".ai.py", delete=False, mode='w', encoding='utf-8', dir=code_cwd) tmp_file.write(code) @@ -150,7 +151,8 @@ def format_error(e): def log_memory_access(path): if 'memory' not in path: return - stats_file = 'memory/file_access_stats.json' + script_dir = os.path.dirname(os.path.abspath(__file__)) + stats_file = os.path.join(script_dir, 'memory/file_access_stats.json') try: with open(stats_file, 'r', encoding='utf-8') as f: stats = json.load(f) except: stats = {} @@ -493,8 +495,9 @@ class GenericAgentHandler(BaseHandler): def get_global_memory(): prompt = "\n" try: - with open('memory/global_mem_insight.txt', 'r', encoding='utf-8') as f: insight = f.read() - with open('assets/insight_fixed_structure.txt', 'r', encoding='utf-8') as f: structure = f.read() + script_dir = os.path.dirname(os.path.abspath(__file__)) + with open(os.path.join(script_dir, 'memory/global_mem_insight.txt'), 'r', encoding='utf-8') as f: insight = f.read() + with open(os.path.join(script_dir, 'assets/insight_fixed_structure.txt'), 'r', encoding='utf-8') as f: structure = f.read() prompt += f"\n[Memory]\n" prompt += f'cwd = {os.path.abspath("./temp")} (用./引用)\n' prompt += structure + '\n../memory/global_mem_insight.txt:\n' diff --git a/launch.pyw b/launch.pyw index c4b78a9..6850e46 100644 --- a/launch.pyw +++ b/launch.pyw @@ -15,7 +15,8 @@ def get_screen_width(): def start_streamlit(port): global proc - cmd = [sys.executable, "-m", "streamlit", "run", "stapp.py", "--server.port", str(port), "--server.address", "localhost", "--server.headless", "true", "--theme.base", "dark"] # 暗黑模式 + script_dir = os.path.dirname(os.path.abspath(__file__)) + cmd = [sys.executable, "-m", "streamlit", "run", os.path.join(script_dir, "stapp.py"), "--server.port", str(port), "--server.address", "localhost", "--server.headless", "true", "--theme.base", "dark"] # 暗黑模式 proc = subprocess.Popen(cmd) atexit.register(proc.kill) @@ -72,7 +73,8 @@ if __name__ == '__main__': threading.Thread(target=start_streamlit, args=(port,), daemon=True).start() if args.tg: - tgproc = subprocess.Popen([sys.executable, "tgapp.py"], creationflags=subprocess.CREATE_NO_WINDOW if os.name=='nt' else 0) + script_dir = os.path.dirname(os.path.abspath(__file__)) + tgproc = subprocess.Popen([sys.executable, os.path.join(script_dir, "tgapp.py")], creationflags=subprocess.CREATE_NO_WINDOW if os.name=='nt' else 0) atexit.register(tgproc.kill) print('[Launch] Telegram Bot started') else: print('[Launch] Telegram Bot not enabled (use --tg to start)') @@ -80,7 +82,8 @@ if __name__ == '__main__': if not args.no_sched: try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM); sock.bind(('127.0.0.1', 45762)); sock.listen(1) - scheduler_proc = subprocess.Popen([sys.executable, "agentmain.py", "--scheduled", "--llm_no", str(args.llm_no)], creationflags=subprocess.CREATE_NO_WINDOW if os.name=='nt' else 0); + script_dir = os.path.dirname(os.path.abspath(__file__)) + scheduler_proc = subprocess.Popen([sys.executable, os.path.join(script_dir, "agentmain.py"), "--scheduled", "--llm_no", str(args.llm_no)], creationflags=subprocess.CREATE_NO_WINDOW if os.name=='nt' else 0); atexit.register(lambda: (scheduler_proc.kill(), sock.close())) print('[Launch] Task Scheduler started') except OSError: diff --git a/reflect/scheduler.py b/reflect/scheduler.py index c3605c5..d9d5f11 100644 --- a/reflect/scheduler.py +++ b/reflect/scheduler.py @@ -4,7 +4,8 @@ from datetime import datetime INTERVAL = 60 # 原版 55+random*10 ONCE = False -PENDING = './sche_tasks/pending' +script_dir = os.path.dirname(os.path.abspath(__file__)) +PENDING = os.path.join(script_dir, '../sche_tasks/pending') def check(): if not os.path.isdir(PENDING): return None @@ -12,6 +13,6 @@ def check(): for f in os.listdir(PENDING): m = re.match(r'(\d{4}-\d{2}-\d{2})_(\d{4})_', f) if m and now >= datetime.strptime(f'{m[1]} {m[2]}', '%Y-%m-%d %H%M'): - raw = open(f'{PENDING}/{f}', encoding='utf-8').read() + raw = open(os.path.join(PENDING, f), encoding='utf-8').read() return f'按scheduled_task_sop执行任务文件 ../sche_tasks/pending/{f}(立刻移到running)\n内容:\n{raw}' return None \ No newline at end of file diff --git a/sidercall.py b/sidercall.py index 6f82de5..ad38aad 100644 --- a/sidercall.py +++ b/sidercall.py @@ -390,7 +390,8 @@ class ToolClient: def chat(self, messages, tools=None): full_prompt = self._build_protocol_prompt(messages, tools) print("Full prompt length:", len(full_prompt), 'chars') - with open(f'./temp/model_responses_{os.getpid()}.txt', 'a', encoding='utf-8', errors="replace") as f: + script_dir = os.path.dirname(os.path.abspath(__file__)) + with open(os.path.join(script_dir, f'./temp/model_responses_{os.getpid()}.txt'), 'a', encoding='utf-8', errors="replace") as f: f.write(f"=== Prompt === {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n{full_prompt}\n") gen = self.backend.ask(full_prompt, stream=True) raw_text = ''; summarytag = '[NextWillSummary]' @@ -400,7 +401,8 @@ class ToolClient: print('Complete response received.') if raw_text.endswith(summarytag): self.last_tools = ''; raw_text = raw_text[:-len(summarytag)] - with open(f'./temp/model_responses_{os.getpid()}.txt', 'a', encoding='utf-8', errors="replace") as f: + script_dir = os.path.dirname(os.path.abspath(__file__)) + with open(os.path.join(script_dir, f'./temp/model_responses_{os.getpid()}.txt'), 'a', encoding='utf-8', errors="replace") as f: f.write(f"=== Response === {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n{raw_text}\n\n") return self._parse_mixed_response(raw_text) diff --git a/stapp.py b/stapp.py index f703c12..a86dc5a 100644 --- a/stapp.py +++ b/stapp.py @@ -80,18 +80,19 @@ def agent_backend_stream(prompt): if "messages" not in st.session_state: st.session_state.messages = [] for msg in st.session_state.messages: - with st.chat_message(msg["role"]): st.markdown(msg["content"]) + with st.chat_message(msg["role"]): st.markdown(msg["content"], unsafe_allow_html=True) if prompt := st.chat_input("请输入指令"): st.session_state.messages.append({"role": "user", "content": prompt}) - with st.chat_message("user"): st.markdown(prompt) + #允许消息内容中包含 HTML 代码并直接渲染,但注意这会有 XSS 安全风险,仅在内容可信时使用 + with st.chat_message("user"): st.markdown(prompt, unsafe_allow_html=True) with st.chat_message("assistant"): message_placeholder = st.empty() response = '' for response in agent_backend_stream(prompt): - message_placeholder.markdown(response + "▌") - message_placeholder.markdown(response) + message_placeholder.markdown(response + "▌", unsafe_allow_html=True) + message_placeholder.markdown(response, unsafe_allow_html=True) st.session_state.messages.append({"role": "assistant", "content": response}) st.session_state.last_reply_time = int(time.time())