Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ Agent 会自己读代码、找出需要的包、全部装好。
python3 launch.pyw
```

Windows 原生桌面窗口启动失败时,可运行 `python3 launch.pyw --browser` 使用浏览器版 Streamlit UI。

启动后会出现一个桌面悬浮窗,直接在里面输入任务指令。

### 可选:让 Agent 帮你做的事
Expand Down Expand Up @@ -266,4 +268,4 @@ GenericAgent 不预设技能,而是**靠使用进化**。每完成一个新任

> Agent 会自动 pull 最新代码并解读 commit log,告诉你新增了什么能力。

> 更多细节请参阅 [README.md](README.md) 或 [详细版图文教程](https://my.feishu.cn/wiki/CGrDw0T76iNFuskmwxdcWrpinPb)。
> 更多细节请参阅 [README.md](README.md) 或 [详细版图文教程](https://my.feishu.cn/wiki/CGrDw0T76iNFuskmwxdcWrpinPb)。
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ cp mykey_template.py mykey.py
python launch.pyw
```

Windows native shell fallback: if the desktop window fails during pywebview startup, run `python launch.pyw --browser` to use the Streamlit browser UI.

> GenericAgent is meant to grow its environment through the Agent itself, not by pre-installing every possible package.

Full guide: [GETTING_STARTED.md](GETTING_STARTED.md)
Expand Down Expand Up @@ -318,6 +320,8 @@ cp mykey_template.py mykey.py
python launch.pyw
```

Windows 原生桌面窗口启动失败时,可运行 `python launch.pyw --browser` 使用浏览器版 Streamlit UI。

> GenericAgent 更推荐由 Agent 在使用中自举环境,而不是预先手动装完整依赖。

完整引导流程见 [GETTING_STARTED.md](GETTING_STARTED.md)。
Expand Down
66 changes: 52 additions & 14 deletions launch.pyw
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import webview, threading, subprocess, sys, time, os, ctypes, atexit, socket, random
import threading, subprocess, sys, time, os, ctypes, atexit, socket, random, webbrowser, urllib.request

WINDOW_WIDTH, WINDOW_HEIGHT, RIGHT_PADDING, TOP_PADDING = 600, 900, 0, 100

script_dir = os.path.dirname(os.path.abspath(__file__))
frontends_dir = os.path.join(script_dir, "frontends")
window = proc = None

def find_free_port(lo=18501, hi=18599):
ports = list(range(lo, hi+1)); random.shuffle(ports)
Expand All @@ -22,6 +23,28 @@ def start_streamlit(port):
proc = subprocess.Popen(cmd)
atexit.register(proc.kill)

def wait_for_streamlit(url, timeout=20):
deadline = time.time() + timeout
while time.time() < deadline:
try:
with urllib.request.urlopen(url, timeout=0.5): return True
except Exception: time.sleep(0.5)
return False

def keep_streamlit_alive():
try:
while proc and proc.poll() is None: time.sleep(1)
except KeyboardInterrupt:
if proc and proc.poll() is None: proc.kill()

def open_browser_mode(port, reason=None):
url = f'http://localhost:{port}'
if reason: print(f'[Launch] Native window unavailable: {reason}')
wait_for_streamlit(url)
print(f'[Launch] Opening browser: {url}')
webbrowser.open(url)
keep_streamlit_alive()

def inject(text):
window.evaluate_js(f"""
const textarea = document.querySelector('textarea[data-testid="stChatInputTextArea"]');
Expand Down Expand Up @@ -62,9 +85,10 @@ PASTE_HOOK_JS = """if (!window._pasteHooked) { window._pasteHooked = true;
}, true);
}"""

def idle_monitor():
def idle_monitor(stop_event=None):
last_trigger_time = 0
while True:
if stop_event and stop_event.is_set(): return
time.sleep(5)
try:
window.evaluate_js(PASTE_HOOK_JS)
Expand All @@ -78,10 +102,32 @@ def idle_monitor():
except Exception as e:
print(f'[Idle Monitor] Error: {e}')

def start_native_window(port):
global window
if os.name == 'nt': os.environ.setdefault('PYTHONNET_RUNTIME', 'coreclr')
import webview
if os.name == 'nt':
screen_width = get_screen_width()
x_pos = screen_width - WINDOW_WIDTH - RIGHT_PADDING
else: x_pos = 100
time.sleep(2)
window = webview.create_window(
title='GenericAgent', url=f'http://localhost:{port}',
width=WINDOW_WIDTH, height=WINDOW_HEIGHT, x=x_pos, y=TOP_PADDING,
resizable=True, text_select=True)
monitor_stop = threading.Event()
monitor_thread = threading.Thread(target=idle_monitor, args=(monitor_stop,), daemon=True)
monitor_thread.start()
try: webview.start()
except Exception:
monitor_stop.set()
raise

if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('port', nargs='?', default='0');
parser.add_argument('--browser', action='store_true', help='Open Streamlit in the default browser')
parser.add_argument('--tg', action='store_true', help='启动 Telegram Bot');
parser.add_argument('--qq', action='store_true', help='启动 QQ Bot');
parser.add_argument('--feishu', '--fs', dest='feishu', action='store_true', help='启动 Feishu Bot');
Expand Down Expand Up @@ -130,15 +176,7 @@ if __name__ == '__main__':
print('[Launch] Task Scheduler started (duplicate prevented by scheduler port lock)')
else: print('[Launch] Task Scheduler not enabled (--sched)')

monitor_thread = threading.Thread(target=idle_monitor, daemon=True)
monitor_thread.start()
if os.name == 'nt':
screen_width = get_screen_width()
x_pos = screen_width - WINDOW_WIDTH - RIGHT_PADDING
else: x_pos = 100
time.sleep(2)
window = webview.create_window(
title='GenericAgent', url=f'http://localhost:{port}',
width=WINDOW_WIDTH, height=WINDOW_HEIGHT, x=x_pos, y=TOP_PADDING,
resizable=True, text_select=True)
webview.start()
if args.browser: open_browser_mode(port)
else:
try: start_native_window(port)
except Exception as e: open_browser_mode(port, e)