feat(stapp): empty-key boot and portal hot-reload wait

Allow GenericAgent to start with empty mykey, watch subscription
portal writes via list_llms, and app-rerun so sidebar LLM select
refreshes without a manual click. Also harden mykey reimport and
note the fastest tmwebdriver autofill login path.
This commit is contained in:
Liang Jiaqing committed 2026-07-22 20:33:37 +08:00
1 parent 8a75b39bf8
commit 400c9ef34f
4 files changed
+36 -21

No files matched your search

+5 -4
View File
@@ -55,6 +55,7 @@ class GenericAgent:
self.force_non_stream = False
logid = f'{(time.time_ns() + random.randrange(1_000_000)) % 1_000_000:06d}'
self.log_path = os.path.join(script_dir, f'temp/model_responses/model_responses_{logid}.txt')
self.llmclient = None
self.load_llm_sessions()
self.extra_sys_prompts = []
self.intervene = self.extrakeyinfo = None
@@ -79,20 +80,20 @@ class GenericAgent:
else: llm_sessions[i] = ToolClient(mixin)
except Exception as e: print(f'\n\n\n[ERROR] Failed to init MixinSession with cfg {s["mixin_cfg"]}: {e}!!!\n\n')
self.llmclients = llm_sessions
if not self.llmclients: return
self.llmclient = self.llmclients[self.llm_no%len(self.llmclients)]
if oldhistory: self.llmclient.backend.history = oldhistory
def next_llm(self, n=-1):
self.load_llm_sessions()
if not self.llmclients: return
self.llm_no = ((self.llm_no + 1) if n < 0 else n) % len(self.llmclients)
lastc = self.llmclient
self.llmclient = self.llmclients[self.llm_no]
try: self.llmclient.backend.history = lastc.backend.history
except: raise Exception('[ERROR] BAD Mixin config: Check your mykey.py')
self.llmclient.last_tools = ''
name = self.get_llm_name(model=True)
if 'glm' in name or 'minimax' in name or 'kimi' in name: load_tool_schema('_cn')
else: load_tool_schema()
load_tool_schema()
def list_llms(self):
self.load_llm_sessions()
return [(i, self.get_llm_name(b), i == self.llm_no) for i, b in enumerate(self.llmclients)]
@@ -100,7 +101,7 @@ class GenericAgent:
b = self.llmclient if b is None else b
if isinstance(b, dict): return 'BADCONFIG_MIXIN'
if model: return b.backend.model.lower()
return f"{type(b.backend).__name__}/{b.backend.name}"
return f"{type(b.backend).__name__.replace('Session', '')}/{b.backend.name}"
def get_ctx_multiplier(self): return getattr(self.llmclient.backend, 'maxlen_multiplier', 1.0)
def abort(self):
+24 -14
View File
@@ -15,7 +15,7 @@ import streamlit as st
import time, json, re, threading, queue
from datetime import timedelta
import agentmain
from agentmain import GeneraticAgent
from agentmain import GenericAgent
import chatapp_common # activate /continue command (monkey patches GeneraticAgent)
from continue_cmd import handle_frontend_command, reset_conversation, list_sessions, extract_ui_messages
from btw_cmd import handle_frontend_command as btw_handle_frontend
@@ -73,19 +73,28 @@ def T(key): return I18N.get(LANG, I18N['zh']).get(key, key)
@st.cache_resource
def init():
agent = GeneraticAgent()
if agent.llmclient is not None:
threading.Thread(target=agent.run, daemon=True).start()
agent = GenericAgent()
threading.Thread(target=agent.run, daemon=True).start()
return agent
agent = init()
if agent.llmclient is None:
_sp = getattr(agentmain, "start_subscription_portal", None)
if _sp:
_sp()
st.warning(T("reopen_page"))
else:
st.error(T("need_mykey"))
_sp = getattr(agentmain, "start_subscription_portal", None)
@st.fragment(run_every=timedelta(seconds=2))
def _watch_portal():
b = st.session_state["portal_wait"]
c = tuple(n for _, n, _ in agent.list_llms())
if c and c != b:
del st.session_state["portal_wait"]
st.session_state.pop("sidebar_llm_select", None)
st.rerun(scope="app")
if not agent.llmclients and _sp:
st.session_state.setdefault("portal_wait", ()); _sp()
if "portal_wait" in st.session_state: _watch_portal()
if not agent.llmclients:
st.warning(T("reopen_page")) if _sp else st.error(T("need_mykey"))
st.stop()
def build_prompt(objective):
@@ -101,7 +110,7 @@ def build_prompt(objective):
def get_controller():
b = {'ev': threading.Event(), 'obj': '', 'out': None, 'ready': False}
def loop():
ag = GeneraticAgent(); ag.verbose = False; ag.log_path = False
ag = GenericAgent(); ag.verbose = False; ag.log_path = False
threading.Thread(target=ag.run, daemon=True).start()
while True:
b['ev'].wait(); b['ev'].clear()
@@ -185,10 +194,11 @@ def render_sidebar():
st.session_state.autonomous_enabled = True
st.toast("✅"); st.rerun(scope="app")
st.caption(T('auto_off_cap'))
if (_sp := getattr(agentmain, "start_subscription_portal", None)):
if _sp:
st.divider()
if st.button(T("get_token")):
_sp(); st.toast(T("get_token_toast"))
st.session_state.portal_wait = tuple(n for _, n, _ in agent.list_llms())
_sp(); st.rerun(scope="app")
with st.sidebar: render_sidebar()
def fold_turns(text):
+6 -2
View File
@@ -8,7 +8,8 @@ if _ROOT not in sys.path: sys.path.append(_ROOT)
def _load_mykeys():
global _mykey_path
try:
import mykey; importlib.reload(mykey); _mykey_path = mykey.__file__
sys.modules.pop('mykey', None)
import mykey; _mykey_path = mykey.__file__
return {k: v for k, v in vars(mykey).items() if not k.startswith('_')}
except ImportError as e:
if getattr(e, 'name', None) != 'mykey':
@@ -21,13 +22,15 @@ def _load_mykeys():
if isinstance(mk, dict) and 'remote_url' in mk: return requests.get(mk['remote_url'], timeout=10).json()
return mk
_mykey_lock = threading.Lock()
_mykey_path = _mykey_mtime = None
def reload_mykeys():
global _mykey_mtime
try:
mt = os.stat(_mykey_path).st_mtime_ns if _mykey_path else -1
if mt == _mykey_mtime: return globals().get('mykeys', {}), False
mk = _load_mykeys(); _mykey_mtime = os.stat(_mykey_path).st_mtime_ns
with _mykey_lock: mk = _load_mykeys()
_mykey_mtime = os.stat(_mykey_path).st_mtime_ns
print(f'[Info] Load mykeys from {_mykey_path}')
globals().update(mykeys=mk)
return mk, True
@@ -1122,6 +1125,7 @@ class NativeToolClient:
def resolve_session(cfg_name):
cfg = reload_mykeys()[0].get(cfg_name)
if not cfg: raise ValueError(f"Config '{cfg_name}' not in mykey")
cfg['_mykey_name'] = cfg_name
if 'native' in cfg_name: return (NativeClaudeSession if 'claude' in cfg_name else NativeOAISession)(cfg=cfg)
if 'claude' in cfg_name: return ClaudeSession(cfg=cfg)
return LLMSession(cfg=cfg) if 'oai' in cfg_name else None
+1 -1
View File
@@ -117,7 +117,7 @@ web_execute_js script='{"cmd": "batch", "commands": [...]}'
## autofill获取与登录
检测:web_scan输出input带`data-autofilled="true"`,value显示为受保护提示(非真实值,Chrome安全保护需点击释放)
- ⚠**前置条件:必须先CDP `Page.bringToFront` 切tab到前台**,Chrome仅在前台tab释放autofill保护值,后台tab物理点击无效
- ⭐**一键释放与登录**:bringToFront → mousePressed点任一字段(无需Released,一个释放全页) → 等500ms → 补input/change事件 → 点登录
- ⭐**最快登录(✅已验证)**:bringToFront → 无视按钮`disabled`,直接CDP完整点击登录按钮(无需处理字段);失败再兜底:mousePressed任一字段 → 等500ms → 补input/change → 点登录
## 验证码/页面视觉截图
- ⭐首选CDP截图:`Page.captureScreenshot`(format:'png')→返回base64,无需前台/后台tab也行,全页高清