189 lines
7.9 KiB
Python
189 lines
7.9 KiB
Python
import asyncio
|
||
import os
|
||
import re
|
||
import shlex
|
||
from datetime import datetime
|
||
from playwright.async_api import async_playwright
|
||
|
||
# [2026-02-11] Version: 1.9.3-Mad-Auto-Heal-Full
|
||
# [2025-07-04] Embed: Version info
|
||
# [2025-06-15] Format: Markdown Standard
|
||
|
||
class MadGeminiAgent:
|
||
def __init__(self, context, page):
|
||
self.context = context
|
||
self.page = page
|
||
self.input_selector = "div[role='textbox']"
|
||
self.container_name = "sandbox_container"
|
||
self.yolo_mode = False
|
||
self.max_iterations = 10
|
||
|
||
async def execute_in_sandbox(self, script):
|
||
"""コンテナへの電気信号送信"""
|
||
docker_cmd = f"docker exec -e OLLAMA_HOST=host.docker.internal -w /workspace {self.container_name} bash -c {shlex.quote(script)}"
|
||
proc = await asyncio.create_subprocess_shell(
|
||
docker_cmd,
|
||
stdout=asyncio.subprocess.PIPE,
|
||
stderr=asyncio.subprocess.PIPE
|
||
)
|
||
stdout, stderr = await proc.communicate()
|
||
return stdout.decode('utf-8', errors='replace'), stderr.decode('utf-8', errors='replace')
|
||
|
||
async def wait_for_gemini_stable(self):
|
||
"""Geminiの打鍵が止まるまで粘り強く監視"""
|
||
print("🧠 Gemini思考中", end="", flush=True)
|
||
last_text = ""
|
||
stable_count = 0
|
||
for _ in range(120):
|
||
responses = await self.page.query_selector_all(".model-response-text")
|
||
if responses:
|
||
current_text = await responses[-1].inner_text()
|
||
if current_text == last_text and len(current_text) > 0:
|
||
stable_count += 1
|
||
else:
|
||
stable_count = 0
|
||
last_text = current_text
|
||
if stable_count >= 3:
|
||
await asyncio.sleep(1)
|
||
break
|
||
print(".", end="", flush=True)
|
||
await asyncio.sleep(1)
|
||
print(" 完了!")
|
||
|
||
def auto_heal_command(self, code):
|
||
"""
|
||
【外科手術】Geminiが壊したBash/C結合構文を物理的に修理する
|
||
"""
|
||
# 1. 'cat << 'EOF' > file.c#include' のように # が密着している場合、改行を挿入
|
||
code = re.sub(r"(cat <<\s*['\"]?EOF['\"]?\s*>\s*[^\s]+\.[a-z0-9]+)#", r"\1\n#", code)
|
||
|
||
# 2. 'EOF#' や 'EOFgcc' のように終端文字が次行とくっついている場合を分離
|
||
code = re.sub(r"EOF#", "EOF\n#", code)
|
||
code = re.sub(r"EOFgcc", "EOF\ngcc", code)
|
||
code = re.sub(r"EOF/", "EOF\n/", code)
|
||
|
||
# 3. その他、極端なワンライナー化の形跡があれば修正
|
||
# (必要に応じてここへ正規表現を追加)
|
||
return code
|
||
|
||
async def extract_commands(self, response_element):
|
||
"""HTMLから実行可能なコマンドを抽出し、Auto-Healを適用"""
|
||
code_elements = await response_element.query_selector_all("code")
|
||
results = []
|
||
for el in code_elements:
|
||
code_text = (await el.inner_text()).strip()
|
||
|
||
# 単語のみのタグは無視
|
||
if "\n" not in code_text and " " not in code_text:
|
||
continue
|
||
|
||
# --- Auto-Heal 適用 ---
|
||
code_text = self.auto_heal_command(code_text)
|
||
|
||
# 言語ラベルの除去
|
||
lines = code_text.split('\n')
|
||
if lines and lines[0].lower() in ['bash', 'sh', 'shell', 'c', 'python', 'cpp']:
|
||
code_text = '\n'.join(lines[1:])
|
||
|
||
if code_text.strip():
|
||
results.append(code_text.strip())
|
||
return results
|
||
|
||
async def send_feedback(self, text):
|
||
"""Geminiへのフィードバック送信"""
|
||
try:
|
||
target = self.page.locator(self.input_selector)
|
||
await target.wait_for(state="visible", timeout=10000)
|
||
await target.scroll_into_view_if_needed()
|
||
await target.click()
|
||
await target.fill(text)
|
||
await self.page.keyboard.press("Enter")
|
||
return True
|
||
except Exception as e:
|
||
print(f"\n⚠️ 通信障害: {e}")
|
||
return False
|
||
|
||
async def run_loop(self):
|
||
print(f"\n[{datetime.now().strftime('%H:%M:%S')}] 🧪 実験室オンライン(Heal-Engine 1.0)。")
|
||
|
||
while True:
|
||
mode_str = "☢️ YOLO" if self.yolo_mode else "🛡️ NORMAL"
|
||
user_input = input(f"\n🧬 Master [{mode_str}] > ").strip()
|
||
|
||
if not user_input: continue
|
||
if user_input.lower() in ["exit", "quit"]: break
|
||
|
||
if user_input.lower() == "yolo on":
|
||
self.yolo_mode = True
|
||
print("☢️ YOLOモード:自動修復・自動実行を開始。")
|
||
continue
|
||
elif user_input.lower() == "yolo off":
|
||
self.yolo_mode = False
|
||
print("🛡️ 安全装置:再起動。")
|
||
continue
|
||
|
||
await self.send_feedback(user_input)
|
||
|
||
iteration = 0
|
||
while iteration < self.max_iterations:
|
||
iteration += 1
|
||
await self.wait_for_gemini_stable()
|
||
|
||
responses = await self.page.query_selector_all(".model-response-text")
|
||
if not responses: break
|
||
|
||
commands = await self.extract_commands(responses[-1])
|
||
if not commands:
|
||
raw_text = await responses[-1].inner_text()
|
||
print(f"🤖 Gemini: {raw_text[:80].replace(chr(10), ' ')}...")
|
||
break
|
||
|
||
print(f"\n⚠️ {len(commands)}件の命令を確認。")
|
||
all_results = ""
|
||
|
||
for i, code in enumerate(commands):
|
||
if self.yolo_mode:
|
||
print(f"🚀 [YOLO] Auto-executing {i+1}/{len(commands)}...")
|
||
ans = 'y'
|
||
else:
|
||
print(f"\n--- 🧪 実行案 [{i+1}/{len(commands)}] ---\n{code}\n----------------")
|
||
ans = input("▶️ 実行許可? (y/n/skip): ").lower()
|
||
|
||
if ans == 'y':
|
||
print("📡 信号送信中...")
|
||
out, err = await self.execute_in_sandbox(code)
|
||
|
||
print(f"\n┏━━━━━━━━━━━━ 実行結果 ━━━━━━━━━━━━┓")
|
||
print(f"┃ [STDOUT]\n{out if out else '(空)'}")
|
||
if err:
|
||
print(f"┃ [STDERR]\n{err}")
|
||
print(f"┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")
|
||
|
||
all_results += f"Command: {code}\nResult: {out}\nError: {err}\n"
|
||
elif ans == 'skip': continue
|
||
else: break
|
||
|
||
if all_results:
|
||
print(f"🔄 フィードバック送信中... ({iteration}/{self.max_iterations})")
|
||
feedback = f"実行結果だ。修正が必要ならコードを出せ。成功なら次のステップへ。\n{all_results}"
|
||
if not await self.send_feedback(feedback): break
|
||
else:
|
||
break
|
||
|
||
async def main():
|
||
async with async_playwright() as p:
|
||
user_data_dir = os.path.expanduser("~/dev/gchat/gemini_profile")
|
||
context = await p.chromium.launch_persistent_context(
|
||
user_data_dir, headless=False,
|
||
args=["--disable-blink-features=AutomationControlled", "--no-sandbox"]
|
||
)
|
||
page = context.pages[0]
|
||
await page.goto("https://gemini.google.com/app")
|
||
agent = MadGeminiAgent(context, page)
|
||
await agent.run_loop()
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
asyncio.run(main())
|
||
except KeyboardInterrupt:
|
||
print("\n☢️ 実験室を封鎖。")
|