28 Commits
Author SHA1 Message Date
Liang Jiaqing 5eac3c1027 refactor: unified mykeys dict, support mykey.py and mykey.json dual format 2026-03-07 23:33:13 +08:00
Liang Jiaqing 61d07a16a6 update: minor fixes to agentmain, mykey_template, sidercall 2026-03-07 22:34:23 +08:00
Liang Jiaqing 7ab47af8e8 fix: zIndex auto coverage bug + monitor maxchars unlimit + autofill CDP sop update 2026-03-07 10:10:26 +08:00
Liang Jiaqing fd9f172588 style: simplify open/write + shorten prompt text 2026-03-06 21:49:43 +08:00
Liang Jiaqing 2156bc6fc6 restore context_win=10000 max_tokens=6144 2026-03-06 21:46:41 +08:00
Liang Jiaqing 959cf6a5a6 refactor: use script_dir for init paths 2026-03-06 21:44:49 +08:00
Liang Jiaqing cccbecb974 Merge PR #6: 火山引擎适配 + 路径修复
# Conflicts:
#	.gitignore
#	agentmain.py
2026-03-06 21:43:04 +08:00
Liang Jiaqing 2272f255f2 feat: add file sending support for telegram bot 2026-03-06 19:19:56 +08:00
Liang Jiaqing 524944dfc8 安全性提升:TID动态生成+Streamlit页面排除+工作记忆修复 2026-03-06 18:54:09 +08:00
Jiaqing Liang c6af92e612 feat: web_scan iframe穿透 + plan_sop术语优化 2026-03-06 00:09:52 +08:00
wangmc1024 ffd08bcd21 允许消息内容中包含HTML代码并直接渲染 | 将纯相对路径改为基于脚本文件的绝对路径,便于全局调用 2026-03-05 22:59:26 +08:00
wangmc1024 19d4e65133 增加了对火山引擎平台的模型适配,避免路径错误 2026-03-05 20:31:05 +08:00
Liang Jiaqing ed75d103f5 优化中间输出频率/plan_sop重构/autofill batch示例/README便携版链接 2026-03-04 17:14:13 +08:00
Liang Jiaqing 313f8a28f0 feat: CDP bridge batch support - cookies/tabs/cdp mixed commands, lazy attach, $N chain refs, file upload & screenshot verified 2026-03-04 13:06:21 +08:00
Liang Jiaqing 3f8407a853 CDP bridge扩展完善+SOP精简+insight修复+plan_sop+autonomous_sop更新 2026-03-04 12:02:50 +08:00
Liang Jiaqing ffe1f3c4c0 fix: update extension display name to TMWD CDP Bridge 2026-03-04 11:16:42 +08:00
Liang Jiaqing 0ab90af250 rename cookie_grabber -> tmwd_cdp_bridge (CDP bridge extension) 2026-03-04 11:15:32 +08:00
Liang Jiaqing dcf65d72c7 launch: bind streamlit to localhost + use high port range 18501-18599 2026-03-03 21:05:34 +08:00
Jiaqing Liang 9219042a8f feat: add --reflect mode with error handling + scheduler/autonomous scripts 2026-03-02 23:25:31 +08:00
Jiaqing Liang 5c3193f326 non-verbose output: exhaust stream silently, yield content only; add plan_sop; subagent_sop cleanup 2026-03-02 23:06:07 +08:00
Jiaqing Liang d010ab1995 refine insight: trim impl details, keep trigger keywords only (1049t) 2026-03-02 21:24:54 +08:00
Jiaqing Liang 066a2995e4 update start_long_term desc: remove single-call limit, add 15-turn rule 2026-03-02 17:25:37 +08:00
LJQ 82991c4a69 Merge pull request #2 from frank-marecogito/main
chore(gitignore): add mykey.py to ignore sensitive API keys and credentials
2026-03-02 09:49:19 +08:00
Frank Fu 4ba1dcc7a2 chore(gitignore): add mykey.py to ignore sensitive API keys and credentials
Add `mykey.py` to `.gitignore` to prevent accidental commits of files containing API keys or other sensitive credentials.
2026-03-02 02:23:21 +03:00
Jiaqing Liang 2655d79acb Update autonomous_operation_sop.md 2026-03-01 23:19:00 +08:00
Liang Jiaqing f27c067b1d autonomous_sop: 加强autonomous_reports路径防护(SOP文本+checkpoint模板双层) 2026-03-01 23:18:36 +08:00
Liang Jiaqing 75ddf8fd1d ljqCtrl: 兼容RDP断开场景的屏幕尺寸/DPI获取; ljqCtrl_sop: ClientToScreen避坑; .gitignore清理 2026-03-01 23:18:36 +08:00
LJQ 392747798a Merge pull request #1 from redreamality/fix/oai-responses-api-mode
Add responses api_mode support for OpenAI relay
2026-03-01 23:16:47 +08:00
34 changed files with 553 additions and 272 deletions

No files matched your search

+9 -1
View File
@@ -26,6 +26,9 @@ Thumbs.db
auth.json
model_responses.txt
# Sensitive files (API keys, credentials)
mykey.py
tasks/
*.zip
@@ -52,6 +55,9 @@ memory/*
# Subagent SOP
!memory/subagent_sop.md
# Plan SOP
!memory/plan_sop.md
# ADB UI tool
!memory/adb_ui.py
@@ -60,5 +66,7 @@ memory/*
restore_commit.txt
sche_tasks/
model_responses.sync-conflict-*
QUICK_START.md
# CDP Bridge 密钥配置(首次运行自动生成)
assets/tmwd_cdp_bridge/config.js
**log.*
+2
View File
@@ -60,6 +60,8 @@ The agent doesn't just execute — it **learns and remembers**.
## Quick Start
> 💡 **Windows零基础用户**:不知道Python是什么?[下载便携版](http://kw.fudan.edu.cn/resources/PC-Agent-Portable.zip)(19MB,解压即用)
```bash
# 1. Clone
git clone https://github.com/lsdefine/pc-agent-loop.git
+7 -2
View File
@@ -54,8 +54,12 @@ def agent_runner_loop(client, system_prompt, user_input, handler, tools_schema,
yield f"**LLM Running (Turn {turn+1}) ...**\n\n"
if (turn+1) % 10 == 0: client.last_tools = '' # 每10轮重置一次工具描述,避免上下文过大导致的模型性能下降
response_gen = client.chat(messages=messages, tools=tools_schema)
response = yield from response_gen
if verbose: yield '\n\n'
if verbose:
response = yield from response_gen
yield '\n\n'
else:
response = exhaust(response_gen)
yield response.content
if not response.tool_calls:
tool_name, args = 'no_tool', {}
@@ -69,6 +73,7 @@ def agent_runner_loop(client, system_prompt, user_input, handler, tools_schema,
showarg = get_pretty_json(args)
if not verbose and len(showarg) > 200: showarg = showarg[:200] + ' ...'
yield f"🛠️ **正在调用工具:** `{tool_name}` 📥**参数:**\n````text\n{showarg}\n````\n"
handler.current_turn = turn + 1
gen = handler.dispatch(tool_name, args, response)
if verbose:
yield '`````\n'
+65 -25
View File
@@ -9,25 +9,34 @@ 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'))
mem_dir = os.path.join(script_dir, 'memory')
if not os.path.exists(mem_dir): os.makedirs(mem_dir)
mem_txt = os.path.join(mem_dir, 'global_mem.txt')
if not os.path.exists(mem_txt): open(mem_txt, 'w', encoding='utf-8').write('')
mem_insight = os.path.join(mem_dir, 'global_mem_insight.txt')
if not os.path.exists(mem_insight):
t = os.path.join(script_dir, 'assets/global_mem_insight_template.txt')
open(mem_insight, 'w', encoding='utf-8').write(open(t, encoding='utf-8').read() if os.path.exists(t) else '')
cdp_cfg = os.path.join(script_dir, 'assets/tmwd_cdp_bridge/config.js')
if not os.path.exists(cdp_cfg):
open(cdp_cfg, 'w', encoding='utf-8').write(f"const TID = '__ljq_{hex(random.randint(0, 99999999))[2:8]}';")
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()
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():
@@ -35,10 +44,7 @@ class GeneraticAgent:
try:
if 'claude' in k: llm_sessions += [ClaudeSession(api_key=cfg['apikey'], api_base=cfg['apibase'], model=cfg['model'])]
if 'oai' in k: llm_sessions += [LLMSession(
api_key=cfg['apikey'],
api_base=cfg['apibase'],
model=cfg['model'],
proxy=cfg.get('proxy'),
api_key=cfg['apikey'], api_base=cfg['apibase'], model=cfg['model'], proxy=cfg.get('proxy'),
api_mode=cfg.get('api_mode', 'chat_completions'),
max_retries=cfg.get('max_retries', 2),
connect_timeout=cfg.get('connect_timeout', 10),
@@ -46,7 +52,7 @@ class GeneraticAgent:
)]
if 'xai' in k: llm_sessions += [XaiSession(cfg, mykeys.get('proxy', ''))]
if 'sider' in k: llm_sessions += [SiderLLMSession(cfg, default_model=x) for x in \
["gemini-3.0-flash", "claude-haiku-4.5", "kimi-k2"]]
["gemini-3.0-flash", "gpt-5.4"]]
except: pass
if len(llm_sessions) > 0: self.llmclient = ToolClient(llm_sessions, auto_save_tokens=True)
else: self.llmclient = None
@@ -88,11 +94,12 @@ 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:
handler.key_info += '\n[SYSTEM] 如果是新任务,请先更新或清除工作记忆\n'
handler.key_info += '\n[SYSTEM] 若开始新任务,先更新或清除工作记忆\n'
self.handler = handler
self.llmclient.backend = self.llmclient.backends[self.llm_no]
gen = agent_runner_loop(self.llmclient, sys_prompt, raw_query,
@@ -125,6 +132,7 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--scheduled', action='store_true', help='计划任务轮询模式')
parser.add_argument('--task', metavar='IODIR', help='一次性任务模式(文件IO)')
parser.add_argument('--reflect', metavar='SCRIPT', help='反射模式:加载监控脚本,check()触发时发任务')
parser.add_argument('--llm_no', type=int, default=0, help='LLM编号')
args = parser.parse_args()
@@ -134,12 +142,13 @@ 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)):
if 'next' in item and random.random() < 0.05: # 1/20的概率写一次中间结果
if 'next' in item and random.random() < 0.95: # 概率写一次中间结果
with open(f'{d}/output{nround}.txt', 'w', encoding='utf-8') as f: f.write(item.get('next', ''))
with open(f'{d}/output{nround}.txt', 'w', encoding='utf-8') as f: f.write(item['done'] + '\n[ROUND END]\n')
for _ in range(150): # 等reply.txt,5分钟超时
@@ -149,18 +158,49 @@ if __name__ == '__main__':
os.remove(rp); break
else: break
nround = int(nround) + 1 if nround.isdigit() else 1
elif args.scheduled:
elif args.reflect:
import importlib.util
spec = importlib.util.spec_from_file_location('reflect_script', args.reflect)
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
interval = getattr(mod, 'INTERVAL', 5)
once = getattr(mod, 'ONCE', False)
on_done = getattr(mod, 'on_done', None)
print(f'[Reflect] loaded {args.reflect}, interval={interval}s, once={once}')
while True:
time.sleep(interval)
try: task = mod.check()
except Exception as e:
print(f'[Reflect] check() error: {e}'); continue
if task is None: continue
print(f'[Reflect] triggered: {task[:80]}')
dq = agent.put_task(task, source='reflect')
try:
while 'done' not in (item := dq.get(timeout=120)): pass
result = item['done']
except Exception as e:
if once: raise
print(f'[Reflect] drain error: {e}'); result = f'[ERROR] {e}'
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
@@ -177,4 +217,4 @@ if __name__ == '__main__':
if 'done' in item: print(); break
except KeyboardInterrupt:
agent.abort()
print('\n[Interrupted]')
print('\n[Interrupted]')
-24
View File
@@ -1,24 +0,0 @@
// background.js - 保留原有事件 + 新增消息监听
chrome.runtime.onInstalled.addListener(() => {
console.log('Cookie Grabber installed');
});
// content script 请求cookie时的处理
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.type === 'getCookies' && sender.tab) {
const url = sender.tab.url;
// 普通cookie + partitioned cookie 双查合并
chrome.cookies.getAll({url}, cookies => {
console.log('[CookieGrabber] normal cookies:', cookies.map(c => c.name));
chrome.cookies.getAll({url, partitionKey: {topLevelSite: url.match(/^https?:\/\/[^/]+/)[0]}}, pCookies => {
console.log('[CookieGrabber] partitioned cookies:', pCookies.map(c => c.name));
const map = {};
cookies.forEach(c => map[c.name] = c.value);
pCookies.forEach(c => map[c.name] = c.value);
const str = Object.entries(map).map(([k,v]) => k + '=' + v).join('; ');
sendResponse({cookies: str});
});
});
return true; // 异步响应
}
});
-18
View File
@@ -1,18 +0,0 @@
// content.js - MutationObserver 监听触发元素
const TRIGGER_ID = '__ljqcg__';
const obs = new MutationObserver(muts => {
for (const m of muts) {
for (const node of m.addedNodes) {
if (node.nodeType === 1 && node.id === TRIGGER_ID) {
chrome.runtime.sendMessage({type: 'getCookies'}, res => {
if (res && res.cookies) node.textContent = res.cookies;
else node.textContent = '__cg_error__';
});
return;
}
}
}
});
obs.observe(document.documentElement, {childList: true, subtree: true});
-32
View File
@@ -1,32 +0,0 @@
{
"manifest_version": 3,
"name": "Cookie Grabber",
"version": "1.0",
"description": "获取所有 cookies",
"permissions": [
"cookies",
"storage",
"tabs",
"activeTab"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup.html"
},
"host_permissions": [
"<all_urls>"
],
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"content.js"
],
"run_at": "document_idle"
}
]
}
-20
View File
@@ -1,20 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>HttpOnly Cookie Grabber</title>
<script src="popup.js" defer></script>
<style>
body { font-family: Arial, sans-serif; padding: 10px; }
ul { padding-left: 20px; }
li { margin: 5px 0; }
pre { background-color: #f8f8f8; padding: 10px; border: 1px solid #ccc; max-height: 150px; overflow-y: auto; }
button { margin-top: 10px; }
</style>
</head>
<body>
<h1>Get All Cookies</h1>
<ul id="cookiesDisplay">No available cookies</ul>
<button id="refresh">Refresh and Copy Cookies</button>
</body>
</html>
-56
View File
@@ -1,56 +0,0 @@
document.addEventListener('DOMContentLoaded', () => {
// 当刷新按钮被点击时,获取当前页面的 cookies
document.getElementById('refresh').addEventListener('click', fetchCookies);
// 加载时显示当前页面的 cookies
fetchCookies();
});
// 从 cookies 存储中获取当前页面的 cookies
function fetchCookies() {
// 获取当前活动的标签页
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const activeTab = tabs[0]; // 获取活动标签页
const currentUrl = activeTab.url; // 当前页面的 URL
console.log("当前活动的 URL:", currentUrl);
// 双查: 普通 + partitioned
const origin = currentUrl.match(/^https?:\/\/[^\/]+/)[0];
chrome.cookies.getAll({ url: currentUrl }, (cookies) => {
chrome.cookies.getAll({ url: currentUrl, partitionKey: { topLevelSite: origin } }, (pCookies) => {
const map = {};
cookies.forEach(c => map[c.name] = c.value);
pCookies.forEach(c => map[c.name] = c.value);
const allCookies = Object.entries(map);
const cookiesDisplay = document.getElementById('cookiesDisplay');
cookiesDisplay.innerHTML = '';
if (allCookies.length === 0) {
cookiesDisplay.innerHTML = '<li>No available cookies</li>';
} else {
let cookiesString = '';
allCookies.forEach(([name, value]) => {
const li = document.createElement('li');
li.textContent = `${name}: ${value}`;
cookiesDisplay.appendChild(li);
cookiesString += `${name}=${value}; `;
});
console.log('cookies:', allCookies.length);
copyCookiesToClipboard(cookiesString.trim());
}
});
});
});
}
// 将 cookies 复制到剪贴板
function copyCookiesToClipboard(cookiesString) {
// 使用 Clipboard API 复制到剪贴板
navigator.clipboard.writeText(cookiesString)
.then(() => {
console.log("Cookies copied to clipboard:", cookiesString);
})
.catch(err => {
console.error("Unable to copy to clipboard:", err);
});
}
+2 -2
View File
@@ -1,9 +1,9 @@
Facts(L2): ../memory/global_mem.txt | Code: ../ | SOPs(L3): ../memory/*.md or *.py | META-SOP(L0): ../memory/memory_management_sop.md
Insight是索引,L2/L3变更时同步Insight。写记忆前先读META-SOP(L0)。
Insight是极简索引,L2/L3变更时同步Insight,索引必须极简。写记忆前先读META-SOP(L0)。
[CONSTITUTION]
1. 改自身源码先请示;./内可自主实验,允许装包和portable工具。
2. 决策前查记忆库;未查证不断言。
3. 分步执行逐步验证;3次失败请求干预。
3. 分步执行,控制粒度,限制失败半径;3次失败请求干预。
4. 密钥文件仅引用,不读取/移动。
5. 写任何记忆前读META-SOP核验,memory下文件只能patch修改(除非新建)。
+2 -1
View File
@@ -19,7 +19,8 @@
(function() {
'use strict';
const log_prefix = "ljq_driver: ";
if (document.querySelector('[data-testid="stApp"],.stApp')) return;
if (window.self !== window.top) {
window.addEventListener('message',e=>{if(e.data?.type==='ljq_exec'){try{let r=eval(e.data.code);parent.postMessage({type:'ljq_result',id:e.data.id,result:String(r)},'*')}catch(err){parent.postMessage({type:'ljq_result',id:e.data.id,error:err.message},'*')}}});
return;
+9 -4
View File
@@ -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
+95
View File
@@ -0,0 +1,95 @@
// background.js - Cookie + CDP Bridge
chrome.runtime.onInstalled.addListener(() => console.log('CDP Bridge installed'));
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.action === 'cookies') {
handleCookies(msg, sender).then(sendResponse);
return true;
}
if (msg.action === 'cdp') {
handleCDP(msg, sender).then(sendResponse);
return true;
}
if (msg.action === 'batch') {
handleBatch(msg, sender).then(sendResponse);
return true;
}
if (msg.action === 'tabs') {
(async () => {
try {
if (msg.method === 'switch') {
const tab = await chrome.tabs.update(msg.tabId, { active: true });
await chrome.windows.update(tab.windowId, { focused: true });
sendResponse({ ok: true });
} else {
const tabs = await chrome.tabs.query({});
const data = tabs.map(t => ({ id: t.id, url: t.url, title: t.title, active: t.active, windowId: t.windowId }));
sendResponse({ ok: true, data });
}
} catch (e) { sendResponse({ ok: false, error: e.message }); }
})();
return true;
}
});
async function handleCookies(msg, sender) {
try {
const url = msg.url || sender.tab?.url;
const origin = url.match(/^https?:\/\/[^\/]+/)[0];
const all = await chrome.cookies.getAll({ url });
const part = await chrome.cookies.getAll({ url, partitionKey: { topLevelSite: origin } }).catch(() => []);
const merged = [...all];
for (const c of part) {
if (!merged.some(x => x.name === c.name && x.domain === c.domain)) merged.push(c);
}
return { ok: true, data: merged };
} catch (e) {
return { ok: false, error: e.message };
}
}
async function handleBatch(msg, sender) {
const R = [];
let attached = null;
const resolve$N = (params) => JSON.parse(JSON.stringify(params || {}).replace(/"\$(\d+)\.([^"]+)"/g,
(_, i, path) => { let v = R[+i]; for (const k of path.split('.')) v = v[k]; return JSON.stringify(v); }));
try {
for (const c of msg.commands) {
if (c.cmd === 'cookies') {
R.push(await handleCookies(c, sender));
} else if (c.cmd === 'tabs') {
const tabs = await chrome.tabs.query({});
R.push({ ok: true, data: tabs.map(t => ({ id: t.id, url: t.url, title: t.title, active: t.active, windowId: t.windowId })) });
} else if (c.cmd === 'cdp') {
const tabId = c.tabId || msg.tabId || sender.tab?.id;
if (attached !== tabId) {
if (attached) { await chrome.debugger.detach({ tabId: attached }); attached = null; }
await chrome.debugger.attach({ tabId }, '1.3');
attached = tabId;
}
R.push(await chrome.debugger.sendCommand({ tabId }, c.method, resolve$N(c.params)));
} else {
R.push({ ok: false, error: 'unknown cmd: ' + c.cmd });
}
}
if (attached) await chrome.debugger.detach({ tabId: attached });
return { ok: true, results: R };
} catch (e) {
if (attached) try { await chrome.debugger.detach({ tabId: attached }); } catch (_) {}
return { ok: false, error: e.message, results: R };
}
}
async function handleCDP(msg, sender) {
const tabId = msg.tabId || sender.tab?.id;
if (!tabId) return { ok: false, error: 'no tabId' };
try {
await chrome.debugger.attach({ tabId }, '1.3');
const result = await chrome.debugger.sendCommand({ tabId }, msg.method, msg.params || {});
await chrome.debugger.detach({ tabId });
return { ok: true, data: result };
} catch (e) {
try { await chrome.debugger.detach({ tabId }); } catch (_) {}
return { ok: false, error: e.message };
}
}
+31
View File
@@ -0,0 +1,31 @@
new MutationObserver(muts => {
for (const m of muts) for (const n of m.addedNodes) {
if (n.id === TID || (n.querySelector && n.querySelector('#' + TID))) {
const el = n.id === TID ? n : n.querySelector('#' + TID);
handle(el);
}
}
}).observe(document.documentElement, { childList: true, subtree: true });
async function handle(el) {
try {
const req = el.textContent.trim() ? JSON.parse(el.textContent) : { cmd: 'cookies' };
const cmd = req.cmd || 'cookies';
let resp;
if (cmd === 'cookies') {
resp = await chrome.runtime.sendMessage({ action: 'cookies', url: req.url || location.href });
} else if (cmd === 'cdp') {
resp = await chrome.runtime.sendMessage({ action: 'cdp', method: req.method, params: req.params || {}, tabId: req.tabId });
} else if (cmd === 'batch') {
resp = await chrome.runtime.sendMessage({ action: 'batch', commands: req.commands, tabId: req.tabId });
} else if (cmd === 'tabs') {
resp = await chrome.runtime.sendMessage({ action: 'tabs', method: req.method, tabId: req.tabId });
} else {
resp = { ok: false, error: 'unknown cmd: ' + cmd };
}
el.textContent = JSON.stringify(resp);
} catch (e) {
el.textContent = JSON.stringify({ ok: false, error: e.message });
}
}
+28
View File
@@ -0,0 +1,28 @@
{
"manifest_version": 3,
"name": "TMWD CDP Bridge",
"version": "2.0",
"description": "Cookie viewer + CDP bridge",
"permissions": [
"cookies",
"tabs",
"activeTab",
"debugger"
],
"host_permissions": ["<all_urls>"],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["config.js", "content.js"],
"run_at": "document_idle",
"all_frames": true
}
],
"action": {
"default_popup": "popup.html",
"default_title": "TMWD CDP Bridge"
}
}
+19
View File
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body{width:420px;max-height:500px;margin:0;padding:8px;font:12px monospace;background:#1e1e1e;color:#d4d4d4;overflow-y:auto}
h3{margin:4px 0;color:#569cd6}
button{background:#264f78;color:#fff;border:none;padding:4px 12px;cursor:pointer;border-radius:3px;margin-bottom:6px}
button:hover{background:#37699e}
pre{white-space:pre-wrap;word-break:break-all;margin:0;padding:6px;background:#252526;border-radius:3px;max-height:420px;overflow-y:auto}
</style>
</head>
<body>
<h3>🍪 Cookies</h3>
<button id="refresh">刷新</button>
<pre id="out">点击刷新获取 cookies...</pre>
<script src="popup.js"></script>
</body>
</html>
+24
View File
@@ -0,0 +1,24 @@
document.addEventListener('DOMContentLoaded', () => {
const out = document.getElementById('out');
const btn = document.getElementById('refresh');
btn.addEventListener('click', fetchCookies);
fetchCookies();
});
async function fetchCookies() {
const out = document.getElementById('out');
try {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.url) { out.textContent = 'No active tab'; return; }
const resp = await chrome.runtime.sendMessage({ action: 'cookies', url: tab.url });
if (!resp?.ok) { out.textContent = 'Error: ' + (resp?.error || 'unknown'); return; }
if (!resp.data.length) { out.textContent = '(no cookies)'; return; }
// 展示带标记
out.textContent = resp.data.map(c =>
`${c.name}=${c.value}` + (c.httpOnly ? ' [H]' : '') + (c.secure ? ' [S]' : '') + (c.partitionKey ? ' [P]' : '')
).join('\n');
// 自动复制 name=value; 格式到剪贴板
const str = resp.data.map(c => `${c.name}=${c.value}`).join('; ');
await navigator.clipboard.writeText(str);
} catch (e) { out.textContent = 'Error: ' + e.message; }
}
+4 -3
View File
@@ -43,8 +43,9 @@
"name": "web_execute_js",
"description": "万能网页操控工具。通过执行 JavaScript 脚本实现对浏览器的完全控制(如点击、滚动、提取特定数据)。鼓励在有把握情况下(记忆中有selector/做法等)精准使用以减少web_scan调用。执行结果可选择保存到本地文件进行后续分析。",
"parameters": {"type": "object", "properties": {
"script": {"type": "string", "description": "要执行的 JavaScript 代码。"},
"save_to_file": {"type": "string", "description": "可选。将 JS 执行结果(js_return)保存到的文件路径。该功能不支持 await 等异步结果。"}}, "required": ["script"]}
"script": {"type": "string", "description": "要执行的 JavaScript 代码或JS文件路径。"},
"save_to_file": {"type": "string", "description": "结果存文件,适合返回值较长时。不支持await。", "default": ""},
"no_monitor": {"type": "boolean", "description": "跳过页面变更监控,省2-3秒。仅在纯读取信息时设置,页面操作时不要设置。", "default": false}}, "required": ["script"]}
}},
{"type": "function", "function": {
"name": "update_working_checkpoint",
@@ -62,7 +63,7 @@
}},
{"type": "function", "function": {
"name": "start_long_term_update",
"description": "准备开始提炼记忆。发现值得长期记忆的信息(环境事实/用户偏好/避坑经验)时调用此工具。一次用户对话只允许调用一次,已记忆更新或在自主流程内时无需调用。",
"description": "准备开始提炼记忆。发现值得长期记忆的信息(环境事实/用户偏好/避坑经验)时调用此工具。已记忆更新或在自主流程内时无需调用。超15轮完成的任务必须调用以沉淀经验。",
"parameters": {"type": "object", "properties": {}}}
}
]
+18 -10
View File
@@ -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 = {}
@@ -158,7 +160,7 @@ def log_memory_access(path):
stats[fname] = {'count': stats.get(fname, {}).get('count', 0) + 1, 'last': datetime.now().strftime('%Y-%m-%d')}
with open(stats_file, 'w', encoding='utf-8') as f: json.dump(stats, f, indent=2, ensure_ascii=False)
def web_execute_js(script, switch_tab_id=None):
def web_execute_js(script, switch_tab_id=None, no_monitor=False):
"""
执行 JS 脚本来控制浏览器,并捕获结果和页面变化。
script: 要执行的 JavaScript 代码字符串。
@@ -180,7 +182,7 @@ def web_execute_js(script, switch_tab_id=None):
if len(driver.get_all_sessions()) == 0:
return {"status": "error", "msg": "没有可用的浏览器标签页,请先打开一个浏览器标签页,且确认TMWebDriver浏览器tempermonkey插件已安装并启用。"}
if switch_tab_id: driver.default_session_id = switch_tab_id
result = execute_js_rich(script, driver)
result = execute_js_rich(script, driver, no_monitor=no_monitor)
return result
except Exception as e:
return {"status": "error", "msg": format_error(e)}
@@ -247,7 +249,7 @@ class GenericAgentHandler(BaseHandler):
self.parent = parent
self.key_info = ""
self.related_sop = ""
self.cwd = cwd
self.cwd = cwd; self.current_turn = 0
self.history_info = last_history if last_history else []
self.code_stop_signal = []
@@ -313,9 +315,13 @@ class GenericAgentHandler(BaseHandler):
'''
script = args.get("script", "")
if not script: return StepOutcome(None, next_prompt="[Error] Empty script param. Check your tool call arguments.")
abs_path = self._get_abs_path(script.strip())
if os.path.isfile(abs_path):
with open(abs_path, 'r', encoding='utf-8') as f: script = f.read()
save_to_file = args.get("save_to_file", "")
switch_tab_id = args.get("switch_tab_id") or args.get("tab_id")
result = web_execute_js(script, switch_tab_id=switch_tab_id)
no_monitor = args.get("no_monitor", False)
result = web_execute_js(script, switch_tab_id=switch_tab_id, no_monitor=no_monitor)
if save_to_file and "js_return" in result:
content = str(result["js_return"] or '')
abs_path = self._get_abs_path(save_to_file)
@@ -403,8 +409,8 @@ class GenericAgentHandler(BaseHandler):
'''
key_info = args.get("key_info", "")
related_sop = args.get("related_sop", "")
if key_info: self.key_info = key_info
if related_sop: self.related_sop = related_sop
if "key_info" in args: self.key_info = key_info
if "related_sop" in args: self.related_sop = related_sop
yield f"[Info] Updated key_info and related_sop.\n"
yield f"key_info:\n{self.key_info}\n\n"
yield f"related_sop:\n{self.related_sop}\n\n"
@@ -471,6 +477,7 @@ class GenericAgentHandler(BaseHandler):
def _get_anchor_prompt(self):
h_str = "\n".join(self.history_info[-20:])
prompt = f"\n### [WORKING MEMORY]\n<history>\n{h_str}\n</history>"
prompt += f"\nCurrent turn: {self.current_turn}\n"
if self.key_info: prompt += f"\n<key_info>{self.key_info}</key_info>"
if self.related_sop: prompt += f"\n有不清晰的地方请再次读取{self.related_sop}"
try: print(prompt)
@@ -488,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'
+7 -4
View File
@@ -2,7 +2,7 @@ import webview, threading, subprocess, sys, time, os, ctypes, atexit, socket, ra
WINDOW_WIDTH, WINDOW_HEIGHT, RIGHT_PADDING, TOP_PADDING = 600, 900, 0, 300
def find_free_port(lo=8501, hi=8599):
def find_free_port(lo=18501, hi=18599):
ports = list(range(lo, hi+1)); random.shuffle(ports)
for p in ports:
try: s = socket.socket(); s.bind(('127.0.0.1', p)); s.close(); return p
@@ -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.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:
+14 -3
View File
@@ -5,7 +5,18 @@
授权你进行自主行动,只要不对环境造成副作用都可进行。
请先选择核心目标,再选择一个小目标进行。最终探测结果形成报告(含操作申请),待用户回来确认后再进行可能的写入或修改操作。
> **must call update_working_checkpoint**:`自主探索|≤15回合|只有cwd内可写|用户不在(问题存报告)|报告目录:./autonomous_reports/|收尾:重读本SOP确认报告目录+更新history|产出=报告+记忆提案`
---
## 🚫 Step 0(阻塞):写入约束便签 — 未完成禁止进入后续步骤
**必须第一个动作就调用** `update_working_checkpoint`,写入以下内容:
```
自主探索|≤15回合|只有cwd内可写|用户不在(问题存报告)|报告目录:./autonomous_reports/|收尾:重读本SOP确认报告目录+更新history|产出=报告+记忆提案 | 路径警告:autonomous_reports在temp/下,用./autonomous_reports/访问,严禁../memory/autonomous_reports/或../autonomous_reports/!
```
⛔ **跳过 Step 0 = 违规**。历史教训:R175因跳过此步,报告存错路径(Desktop而非./autonomous_reports/)。
---
## 📋 大纲
- 报告目录与规则
@@ -31,7 +42,7 @@
选择规则:
- cwd下有 `TODO.txt` 时优先执行其中任务,完成后移除条目
- 不连续选择相同方向(TODO除外),选择前先读 history.txt 了解近期已做方向
- 方向平衡:近10轮内同一方向≤3次,同一具体主题(如"磁盘健康""书签")不重复
- 方向平衡:近10轮内同一方向≤3次,同一具体主题(如"磁盘健康""书签")≥20轮冷却
- 选定后先声明一句话预期收益,写入报告开头
目标排序(按价值递减):
@@ -45,7 +56,7 @@
选择原则:个性化优先(只有探测这台PC才能获得的知识)→ 盲区优先(自身参数无法复现的知识)→ 假设驱动(明确要验证什么,边探测边实验)→ 禁止低价值验证(不验证静态配置、不做无假设巡检)
探测领域(鼓励发散,不要总盯技术/agent):
- 有效上网冲浪:资源站巡检(果核剥壳/吾爱破解/什么值得买等→结合需求筛选)、深度阅读(≤2话题读正文提炼)
- 有效上网冲浪:资源站巡检(果核剥壳/吾爱破解/什么值得买等→结合需求筛选)、深度阅读(≤2话题读正文提炼)。冲浪发现高价值工具/资源时,下一轮优先实测评估(冲浪→行动链)
- 实用小工具 · 本地环境健康 · 自动化优化 · 了解用户 · 推荐(游戏/视频/工具附理由) · 自身演进 · SOP/记忆维护(别每次都选) · 整理cwd
禁区:❌ Hacker News · 刷新闻头条 · 泛采集标题/无目标刷新闻 · 探索知名工具基础用法 · 调研弱于当前框架的agent · 调研其他web自动化/computer use框架 · 读取自身代码库
+19 -12
View File
@@ -1,4 +1,3 @@
# coding=utf-8
"""
CRITICAL: 严禁在此工具链中 import pyautogui (会污染 win32api 导致逻辑冲突)。
ljqCtrl Quick Reference:
@@ -10,25 +9,33 @@ ljqCtrl Quick Reference:
- MouseDClick(staytime=0.05), MouseClick(staytime=0.05)
"""
import os, sys, time, random, math
import win32api, win32con
import os, sys, time, random, math, win32api, win32con
import numpy as np
dpi_scale = 1
try:
from PIL import ImageGrab, Image, ImageEnhance, ImageFilter, ImageDraw
import cv2
except: pass
scr = ImageGrab.grab()
swidth, sheight = scr.size
print('Screen width & height:', swidth, sheight)
cwidth, cheight = map(win32api.GetSystemMetrics, [win32con.SM_CXSCREEN, win32con.SM_CYSCREEN])
dpi_scale = cwidth / swidth
print('dpi_scale:', dpi_scale)
time.process_time()
try:
scr = ImageGrab.grab()
swidth, sheight = scr.size
print('Screen width & height:', swidth, sheight)
cwidth, cheight = map(win32api.GetSystemMetrics, [win32con.SM_CXSCREEN, win32con.SM_CYSCREEN])
dpi_scale = cwidth / swidth
print('dpi_scale:', dpi_scale)
except:
import ctypes
user32 = ctypes.windll.user32
user32.SetProcessDPIAware() # 确保 DPI 感知
cwidth = user32.GetSystemMetrics(0) # SM_CXSCREEN
cheight = user32.GetSystemMetrics(1) # SM_CYSCREEN
try:
dpi = user32.GetDpiForSystem() # Windows 10 1607+
dpi_scale = dpi / 96.0
except: dpi_scale = 1.0 # 降级方案
print(f'Screen (RDP disconnected): {cwidth}x{cheight}, dpi_scale: {dpi_scale}')
def MouseDown(): win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
def MouseUp(): win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0)
+1
View File
@@ -41,3 +41,4 @@ ljqCtrl.Click(px, py)
- **物理验证**:模拟操作前必须确保窗口已通过 `activate()` 置于前台。
- **偏移量**:所有的相对偏移像素值(如“向右移动 10 像素”)同样需要除以 `dpi_scale`。
- **坐标对齐**: 物理坐标 = 截图坐标;ljqCtrl 自动处理 DPI 换算,禁止手动重复计算。
- **⚠️ 窗口坐标转换陷阱**:使用 `win32gui.GetWindowRect(hwnd)` 获取的矩形包含标题栏和边框,而截图内容是客户区。点击截图内元素时,必须用 `win32gui.ClientToScreen(hwnd, (0, 0))` 获取客户区原点的屏幕坐标,再加上截图内坐标。禁止直接用 GetWindowRect 左上角 + 截图坐标。
+9 -4
View File
@@ -10,6 +10,8 @@
3. **禁止存储易变状态 (No Volatile State)**
* **定义**:严禁存储随时间/会话高频变化的数据。
* **示例**:当前时间戳、临时 Session ID、正在运行的 PID、某个具体绝对路径、连接的设备信息
4. **最小充分指针 (Minimum Sufficient Pointer)**
* 上层只留能定位下层的最短标识,多一词即冗余。
---
## 记忆层级架构
```
@@ -24,11 +26,11 @@ L3: ../memory/ (记录库层 - 包含 .md/.py 等各类文件)
### L1:全局内存索引 (global_mem_insight.txt)
**职责**:为 L2 和 L3 提供极简导航索引,确保关键能力可被发现。
**特征**:
- 体积限制:≤ 30 行(硬约束)
- 体积限制:≤ 30 行(硬约束),< 1k tokens(期望)。严禁填写细节(除非极高频任务)
- 内容:两层「场景关键词→记忆定位」映射 + RULES(红线规则 + 高频犯错点)
- 第一层:高频场景 key→value(直接给出 sop/py/L2 section 名),自包含可只写一个词
- 第一层:高频场景 key→value(直接给出 sop/py/L2 section 名),自包含名称只写一词不重复翻译
- 第二层:低频场景仅列关键词,需要时 read L2 或 ls L3 自行定位
- 作用:触发词,不索引则不知道有此能力
- 核心:场景触发词极重要(不索引则不知有此能力),但严禁写How-to细节
- RULES:压缩版避坑准则,包含:
- 红线规则(致命型):违反会导致进程终止或系统崩溃(如 `禁无条件杀python(会杀自己)`)
- 红线规则(隐蔽型):违反不报错但产生错误结果(如 `搜索用google不用百度`)
@@ -41,7 +43,7 @@ L3: ../memory/ (记录库层 - 包含 .md/.py 等各类文件)
**特征**:
- 趋势:随环境扩展而膨胀(可接受)
- 内容:按 `## [SECTION]` 组织的事实条目
- 同步:变化时更新 L1 的相应 TOPIC 导航行
- 同步:变化时更新 L1 的相应 TOPIC 导航行,只能导航
**禁止**:禁止存储易变状态、禁止存储猜测、严禁存储大模型可推理的通用常识
---
### L3:任务级精简记录库 (../memory/)
@@ -61,6 +63,9 @@ L3: ../memory/ (记录库层 - 包含 .md/.py 等各类文件)
| L2/L3 删除场景 | 删除对应层的关键词/映射行 |
| L2/L3 修改值 | 若不影响场景定位则不动 L1 |
| 发现通用避坑规律 | 压缩为一句加入 RULES |
> **同步红线**:L1 只写关键词/名称,禁搬细节。
---
## 信息分类快速决策树
```
+36
View File
@@ -0,0 +1,36 @@
# Plan Mode SOP
> 简单任务(1-2步可完成)无需本SOP,直接做。
## 0. 基本协议
进入 plan mode 后,立即调用 `update_working_checkpoint`,内容包含:
1. **PLAN**:初始为 `[ ] 规划` `[ ] 执行`,规划完成后展开为详细 checklist
2. **RULES**(以下三条原文写入,每次 update 必须保留):
- 一次只做一步,完成后标 [x] 并 update checkpoint
- 每次 checkpoint 必须保留完整 PLAN + RULES
- 全部 [x] 才可收尾
## 1. 规划阶段
初始 checkpoint:`[ ] 规划` `[ ] 执行`。"规划"完成 = 展开执行步骤并 update checkpoint。
### 1a. 读 SOP
查 insight 找相关 SOP 并读取——SOP 提供任务骨架,直接指导分解。无则跳过。不确定性最高的环节先探,其结果可能推翻整个计划。
### 1b. 分类
从依赖关系读出结构:有依赖→Sequential | 无依赖→MAP | 条件未知→Branch。可嵌套。
### 1c. 分解
按结构展开 checklist。**每个子任务必须具体列出,禁止笼统描述**(如"批量处理所有文件"必须展开为每个文件的具体条目)。每步须有独立完成判据,否则继续拆。update checkpoint 并标 `[x] 规划`。
- Seq: `[ ]A → [ ]B → [ ]C`
- MAP: `[ ]D1 [ ]D2 [ ]D3 … → [ ]汇总`(汇总为最后一步,中心化执行)
- Branch: `[ ]尝试X → 成功:[ ]Y / 失败:[ ]Z`
## 2. 执行注意
- 计划有误时回到规划修正,不硬凑;不可逆操作前多验证一步
- MAP 的子任务可用 subagent 并行执行
- 步骤间用尽量用文件传递中间结果和汇总,不靠上下文记忆
- 收尾:全部 `[x]` 后汇总结果,清理 checkpoint
+2 -1
View File
@@ -22,8 +22,9 @@ proc = subprocess.Popen(
- 必须 Popen,禁止 subprocess.run(会阻塞)
- stdout.log/stderr.log 用于调试subagent卡死、LLM调用失败等问题
- `--llm_no` 默认=sonnet 4.5,`--llm_no 1`=opus 4.6
- 文件统一 UTF-8,subagent 无 reply 5min 自动退出无需清理
- **禁止合并启动+轮询到同一个code_run**——会阻塞自己。启动Popen立即返回,下一轮再poll output.txt。这是并行的前提
- 新建/复用任务目录时,先删除旧 output*.txt(否则会读到上次结果误判完成)
## 场景1:测试模式 - 行为验证
**用途**:观察agent真实行为,修正RULES/L2/L3/SOP
+44 -8
View File
@@ -7,13 +7,15 @@
## 限制(isTrusted)
- JS dispatch的事件`isTrusted=false`,敏感操作(文件上传/部分按钮)会被浏览器拦截
- 文件上传:JS无法填充`<input type=file>`,必须ljqCtrl物理点击+Win32轮询文件对话框
- 流程:SetForegroundWindow→ljqCtrl点上传按钮→FindWindow轮询对话框→输入路径→轮询关闭
- 元素→屏幕物理坐标(ljqCtrl点击前必算):JS一次取rect+窗口信息,公式:
- ⭐**首选绕过:CDP桥**——CDP派发的Input事件是浏览器原生级别(isTrusted=true),且无需前台,见下方CDP章节
- 文件上传:JS无法填充`<input type=file>`
- ⭐首选CDP batch:getDocument→querySelector→DOM.setFileInputFiles(无需前台/物理点击)
- 备选ljqCtrl物理点击:SetForegroundWindow→点上传按钮→FindWindow轮询对话框→输入路径→轮询关闭
- 备选:元素→屏幕物理坐标(ljqCtrl/PostMessage点击前必算):JS一次取rect+窗口信息,公式:
- `physX = (screenX + rect中心x) * dpr`,`physY = (screenY + chromeH + rect中心y) * dpr`
- chromeH = outerHeight - innerHeight,dpr = devicePixelRatio
- 注意:screenX/Y也是CSS像素,所有值先加后统一乘dpr
- 结论:读信息+普通操作用TMWebDriver;文件上传等敏感操作需配合ljqCtrl
- 结论:读信息+普通操作用TMWebDriver;需isTrusted事件首选CDP桥;文件上传首选CDP三连(备选ljqCtrl)
## 导航
- `web_scan` 仅读当前页不导航,切换网站用 `web_execute_js` + `location.href='url'`
@@ -42,10 +44,44 @@ fetch('PDF_URL').then(r=>r.blob()).then(b=>{
- 已修复:移除TM脚本内轮询,改由Python侧`get_session_dict()`前后对比检测新标签
- 同理:TM脚本中任何后台逻辑都应避免依赖setTimeout轮询
## Cookie提取(含HttpOnly)
前提:需先安装`assets/cookie_grabber/`扩展
机制:注入`id="__ljqcg__"`的div→扩展检测后自动将完整cookie写回该元素textContent(含HttpOnly)## 验证码/页面视觉截图
- 优先:JS `canvas.toDataURL()` 直接拿base64(验证码是canvas/img时最干净,无需截屏)
## CDP桥(tmwd_cdp_bridge扩展) ⭐首选
扩展路径:`assets/tmwd_cdp_bridge/`(需安装,含debugger权限)
⚠TID密钥:首次运行自动生成到`assets/tmwd_cdp_bridge/config.js`(已gitignore),扩展通过manifest引用
调用:MutationObserver监听addedNodes(id=TID),⚠每次必须remove旧→createElement新→设textContent JSON→appendChild
```js
// TID从assets/tmwd_cdp_bridge/config.js读取,示例用'__ljq_ctrl'占位
const old = document.getElementById(TID);
if (old) old.remove();
const el = document.createElement('div');
el.id = TID; el.style.display = 'none';
el.textContent = JSON.stringify({cmd:'...', ...});
document.body.appendChild(el); // 响应写回el.textContent
```
单命令:`{cmd:'tabs'}` | `{cmd:'cookies'}` | `{cmd:'cdp', tabId:N, method:'...', params:{...}}`
- ⭐batch混合:`{cmd:'batch', commands:[{cmd:'cookies'},{cmd:'tabs'},{cmd:'cdp',...},...]}`
- 返回`{ok:true, results:[...]}`,一次请求多命令,CDP懒attach复用session
- `$N.path`引用第N个结果字段(0-indexed),如`"nodeId":"$2.root.nodeId"`
- 典型:文件上传三连 getDocument→querySelector(input[type=file])→setFileInputFiles
- ⚠tabId:CDP默认sender.tab.id(当前注入页),跨tab需显式tabId或先batch内tabs查
- CDP可用任意方法(Input/Network/DOM/Page/Runtime/Emulation等),单条每次attach→send→detach
- ⭐跨tab无需前台:指定tabId即可操作后台标签页
- ⭐绕过isTrusted:CDP派发的Input事件是浏览器原生级别
## autofill获取
检测:web_scan输出input带`data-autofilled="true"`,value显示为受保护提示(非真实值,Chrome安全保护需点击释放)
- ⭐首选CDP单次点击:JS取任一autofill输入框坐标→CDP `Input.dispatchMouseEvent` mousePressed一次即可释放→JS读`.value`
- ⚠点击一个autofill字段会释放页面上**所有**autofill字段的值,无需逐个点击
- ⚠只需mousePressed,不需要mouseReleased配对
- ⚠tabId:当前注入页无需指定(默认sender.tab.id),跨tab才需显式tabId(整数)
- 示例(当前页):`{cmd:'cdp',method:'Input.dispatchMouseEvent',params:{type:'mousePressed',x:X,y:Y,button:'left',clickCount:1}}`
- 示例(跨tab):先`{cmd:'tabs'}`获取tabId(整数),再`{cmd:'cdp',tabId:N,method:'Input.dispatchMouseEvent',params:{...}}`
- ⚠batch的`$N.path`引用会将整数tabId转为字符串导致类型错误,跨tab时建议分两次命令而非batch
- 备选PostMessage物理点击(仅Windows/需前台):枚举Chrome窗口标题匹配→rect*dpr→WM_LBUTTONDOWN/UP到Chrome_RenderWidgetHostHWND子窗口
- 坑:多RenderWidgetHostHWND共存,必须按父窗口标题匹配再取子窗口
## 验证码/页面视觉截图
- ⭐首选CDP截图:`Page.captureScreenshot`(format:'png')→返回base64,无需前台/后台tab也行,全页高清
- 验证码canvas/img:JS `canvas.toDataURL()` 直接拿base64最干净
- 备选:`window.open(location.href,'_blank')` 前台开新标签→win32截图→完后close
- GM_openInTab在web_execute_js不可用(非油猴上下文)
- 浏览器无JS API切标签页,只能开新的来保证前台
+3 -3
View File
@@ -1,7 +1,7 @@
oai_config = {
'apikey':'sk-uklURcj',
'apibase':"http://113.145.139.147:3001",
'apibase':"http://243.55.19.137:2001",
'model':"openai/gpt-5.1",
'api_mode':"chat_completions", # optional: "chat_completions" | "responses"
'max_retries': 2, # optional: retries for 429/timeout/5xx
@@ -16,14 +16,14 @@ sider_cookie = 'token=Bearer%20eyJhbGciOiJIUz...'
# feel free to add more ~
oai_config2 = {
'apikey':'sk-uklURcj...',
'apibase':"http://133.145.139.147:3001",
'apibase':"http://243.55.19.137:2001",
'model':"claude-opus-4-6-20260206"
}
claude_config = {
'apikey':'klURcj...',
'apibase':"http://233.145.139.147:3001",
'apibase':"http://233.85.13.149:2001",
'model':"claude-opus"
}
+6
View File
@@ -0,0 +1,6 @@
# reflect/autonomous.py
INTERVAL = 1800
ONCE = False
def check():
return "[AUTO]🤖 用户已经离开超过30分钟,作为自主智能体,请阅读自动化sop,执行自动任务。"
+18
View File
@@ -0,0 +1,18 @@
import os, re
from datetime import datetime
INTERVAL = 60 # 原版 55+random*10
ONCE = False
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
now = datetime.now()
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(os.path.join(PENDING, f), encoding='utf-8').read()
return f'按scheduled_task_sop执行任务文件 ../sche_tasks/pending/{f}(立刻移到running)\n内容:\n{raw}'
return None
+22 -22
View File
@@ -2,10 +2,15 @@ import os, json, re, time, requests, sys, threading, urllib3
from datetime import datetime
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
try: import mykey
except: raise Exception('[ERROR] mykey.py not found, please copy mykey_template.py to mykey.py and fill your LLM backend.')
def _load_mykeys():
try:
import mykey; return {k: v for k, v in vars(mykey).items() if not k.startswith('_')}
except ImportError: pass
p = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'mykey.json')
if not os.path.exists(p): raise Exception('[ERROR] mykey.py or mykey.json not found, please create one from mykey_template.')
with open(p, encoding='utf-8') as f: return json.load(f)
mykeys = vars(mykey)
mykeys = _load_mykeys()
proxy = mykeys.get("proxy", 'http://127.0.0.1:2082')
proxies = {"http": proxy, "https": proxy} if proxy else None
@@ -37,7 +42,7 @@ class SiderLLMSession:
return full_text
class ClaudeSession:
def __init__(self, api_key, api_base, model="claude-opus", context_win=9000):
def __init__(self, api_key, api_base, model="claude-opus", context_win=10000):
self.api_key, self.api_base, self.default_model, self.context_win = api_key, api_base.rstrip('/'), model, context_win
self.raw_msgs, self.lock = [], threading.Lock()
def _trim_messages(self, messages):
@@ -51,7 +56,7 @@ class ClaudeSession:
else: break
if current > self.context_win * 3.6: print(f'[DEBUG] {len(result)} contexts, whole length {current//4} tokens.')
return result[::-1] or messages[-2:]
def raw_ask(self, messages, model=None, temperature=0.5, max_tokens=4096):
def raw_ask(self, messages, model=None, temperature=0.5, max_tokens=6144):
model = model or self.default_model
headers = {"x-api-key": self.api_key, "Content-Type": "application/json", "anthropic-version": "2023-06-01"}
payload = {"model": model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens, "stream": True}
@@ -100,7 +105,11 @@ class LLMSession:
else: self.api_mode = "chat_completions"
def _endpoint(self, path):
# 处理火山引擎API,它已经包含完整路径
if 'ark.cn-beijing.volces.com' in self.api_base or 'ark.cn-shanghai.volces.com' in self.api_base:
return f"{self.api_base}/{path.lstrip('/')}"
if self.api_base.endswith('/v1'): return f"{self.api_base}/{path.lstrip('/')}"
if self.api_base.endswith('$'): return f"{self.api_base.rstrip('$')}/{path.lstrip('/')}"
return f"{self.api_base}/v1/{path.lstrip('/')}"
def _retry_delay(self, resp, attempt):
@@ -211,9 +220,7 @@ class LLMSession:
try: body = (resp.text or "").strip()
except: body = ""
body = body[:1200] if body else "<empty>"
rid = ""
retry_after = ""
ct = ""
rid = ""; retry_after = ""; ct = ""
try:
h = resp.headers or {}
rid = h.get("x-request-id") or h.get("request-id") or ""
@@ -388,7 +395,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]'
@@ -398,7 +406,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)
@@ -413,7 +422,7 @@ class ToolClient:
### 交互协议 (必须严格遵守,持续有效)
请按照以下步骤思考并行动,标签之间需要回车换行:
1. **思考**: 在 `<thinking>` 标签中先进行思考,分析现状和策略。
2. **总结**: 在 `<summary>` 中输出*极为简短*的高度概括的单行(<30字)物理快照,包括上次工具调用结果获取的新信息+本次工具调用意图和预期。此内容将进入长期工作记忆,记录关键信息,严禁输出无实际信息增量的描述。
2. **总结**: 在 `<summary>` 中输出*极为简短*的高度概括的单行(<30字)物理快照,包括上次工具调用结果产生的新信息+本次工具调用意图。此内容将进入长期工作记忆,记录关键信息,严禁输出无实际信息增量的描述。
3. **行动**: 如需调用工具,请在回复正文之后输出一个 **<tool_use>块**,然后结束,我会稍后给你返回<tool_result>块。
格式: ```<tool_use>\n{{"name": "工具名", "arguments": {{参数}}}}\n</tool_use>\n```
@@ -502,18 +511,9 @@ def tryparse(json_str):
return json.loads(json_str)
if __name__ == "__main__":
import sys, os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
try:
import mykey
except ImportError:
class MockMyKey: pass
mykey = MockMyKey()
mykeys = vars(mykey)
sider_cookie = mykeys.get("sider_cookie")
oai_configs = {
k: v for k, v in vars(mykey).items() if k.startswith("oai_config") and v
k: v for k, v in mykeys.items() if k.startswith("oai_config") and v
}
google_api_key = mykeys.get("google_api_key")
cfg = oai_configs.get("oai_config")
@@ -539,4 +539,4 @@ if __name__ == "__main__":
response = get_final(llmclient.chat(
messages=[{"role": "user", "content": "<tool_result>10.176.45.12</tool_result>"}]
))
print(response.content)
print(response.content)
+24 -6
View File
@@ -18,6 +18,7 @@ function createEnhancedDOMCopy() {
const clone = sourceNode.cloneNode(false);
if ((sourceNode.tagName === 'INPUT' || sourceNode.tagName === 'TEXTAREA') && sourceNode.value) clone.setAttribute('value', sourceNode.value);
else if (sourceNode.tagName === 'SELECT' && sourceNode.value) clone.setAttribute('data-selected', sourceNode.value);
try { if (sourceNode.matches && sourceNode.matches(':-webkit-autofill')) { clone.setAttribute('data-autofilled', 'true'); if (!sourceNode.value) clone.setAttribute('value', '⚠️受保护-读tmwebdriver_sop的autofill章节提取'); } } catch(e) {}
const isDropdown = sourceNode.classList?.contains('dropdown-menu') ||
/dropdown|menu/i.test(sourceNode.className) || sourceNode.getAttribute('role') === 'menu';
@@ -28,6 +29,20 @@ function createEnhancedDOMCopy() {
const childClone = cloneNode(child, keep || isSmallDropdown);
if (childClone) childNodes.push(childClone);
}
if (sourceNode.tagName === 'IFRAME') {
try {
const iDoc = sourceNode.contentDocument || sourceNode.contentWindow?.document;
if (iDoc && iDoc.body && iDoc.body.children.length > 0) {
const wrapper = document.createElement('div');
wrapper.setAttribute('data-iframe-content', sourceNode.src || '');
for (const ch of iDoc.body.childNodes) {
const c = cloneNode(ch, keep);
if (c) wrapper.appendChild(c);
}
if (wrapper.childNodes.length) childNodes.push(wrapper);
}
} catch(e) {}
}
const rect = sourceNode.getBoundingClientRect();
const style = window.getComputedStyle(sourceNode);
@@ -185,7 +200,7 @@ function analyzeNode(node, pPathType='main') {
((minDimensionRatio > 0.2 && rect.width/window.innerWidth < 0.98) || minDimensionRatio > 0.95)) {
top.node.dataset.mark = 'K:mainInteractive';
sorted.slice(1).forEach(e => {
if (e.zIndex < sorted[0].zIndex) {
if ((parseInt(e.zIndex)||0) <= (parseInt(sorted[0].zIndex)||0)) {
e.node.dataset.mark = 'R:covered';
} else {
e.node.dataset.mark = 'K:noncovered';
@@ -840,7 +855,7 @@ def find_changed_elements(before_html, after_html):
result["top_change"] = h if len(h) <= 2000 else h[:2000] + '...[TRUNCATED]'
return result
def get_html(driver, cutlist=False, maxchars=28000, instruction="", extra_js=""):
def get_html(driver, cutlist=False, maxchars=38000, instruction="", extra_js=""):
page = get_main_block(driver, extra_js=extra_js)
soup = optimize_html_for_tokens(page)
html = str(soup)
@@ -859,9 +874,11 @@ def get_html(driver, cutlist=False, maxchars=28000, instruction="", extra_js="")
if len(ss) > maxchars: ss = ss[:maxchars] + ' ... [TRUNCATED]'
return ss
def execute_js_rich(script, driver):
try: last_html = get_html(driver, cutlist=False, extra_js=temp_monitor_js)
except: last_html = None
def execute_js_rich(script, driver, no_monitor=False):
last_html = None
if not no_monitor:
try: last_html = get_html(driver, cutlist=False, extra_js=temp_monitor_js, maxchars=9999999)
except: pass
result = None; error_msg = None; reloaded = False; newTabs = []
before_sids = set(driver.get_session_dict().keys())
try:
@@ -888,12 +905,13 @@ def execute_js_rich(script, driver):
rr['environment']['newTabs'] = newTabs
rr['suggestion'] = "页面已刷新,以上新标签页在执行期间连接。"
if error_msg: rr['error'] = error_msg
if no_monitor: return rr
if not reloaded:
try: rr['transients'] = get_temp_texts(driver)
except: rr['transients'] = []
if not reloaded and len(newTabs) == 0:
try:
current_html = get_html(driver, cutlist=False)
current_html = get_html(driver, cutlist=False, maxchars=9999999)
if last_html is None: raise Exception("no baseline")
diff_data = find_changed_elements(last_html, current_html)
change_count = diff_data.get('changed', 0)
+5 -4
View File
@@ -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())
+28 -7
View File
@@ -8,11 +8,11 @@ try:
except:
print("Please ask the agent install python-telegram-bot to use telegram module.")
sys.exit(1)
import mykey
from sidercall import mykeys
agent = GeneraticAgent()
agent.verbose = False
ALLOWED = set(getattr(mykey, 'tg_allowed_users', []))
ALLOWED = set(mykeys.get('tg_allowed_users', []))
_TAG_PATS = [r'<' + t + r'>.*?</' + t + r'>' for t in ('thinking', 'summary', 'tool_use')]
_TAG_PATS.append(r'<file_content>.*?</file_content>')
@@ -62,18 +62,39 @@ async def _stream(dq, msg):
try: await msg.edit_text(display)
except Exception: pass
last_text = display
if done: break
if done:
files = re.findall(r'\[FILE:([^\]]+)\]', show[-1000:])
for fpath in files:
if os.path.exists(fpath):
if fpath.lower().endswith(('.png','.jpg','.jpeg','.gif','.webp')):
try: await msg.reply_photo(open(fpath,'rb'))
except Exception: pass
else:
try: await msg.reply_document(open(fpath,'rb'))
except Exception: pass
show = re.sub(r'\[FILE:[^\]]+\]', '', show)
if show.strip():
try: await msg.edit_text(_to_html(show), parse_mode='HTML')
except Exception:
try: await msg.edit_text(show)
except Exception: pass
break
async def handle_msg(update, ctx):
uid = update.effective_user.id
if ALLOWED and uid not in ALLOWED:
return await update.message.reply_text("no")
msg = await update.message.reply_text("thinking...")
dq = agent.put_task(update.message.text, source="telegram")
await _stream(dq, msg)
prompt = f"If you need to show files to user, use [FILE:filepath] in your response.\n\n{update.message.text}"
dq = agent.put_task(prompt, source="telegram")
task = asyncio.create_task(_stream(dq, msg))
ctx.user_data['stream_task'] = task
async def cmd_abort(update, ctx):
agent.abort()
task = ctx.user_data.get('stream_task')
if task and not task.done():
task.cancel()
await update.message.reply_text("Aborted")
async def cmd_llm(update, ctx):
@@ -103,7 +124,7 @@ if __name__ == '__main__':
sys.stdout = sys.stderr = _logf
print('[NEW] New process starting, the above are history infos ...')
threading.Thread(target=agent.run, daemon=True).start()
proxy = vars(mykey).get('proxy', 'http://127.0.0.1:2082')
proxy = mykeys.get('proxy', 'http://127.0.0.1:2082')
print('proxy:', proxy)
async def _error_handler(update, context: ContextTypes.DEFAULT_TYPE):
@@ -115,7 +136,7 @@ if __name__ == '__main__':
# Recreate request and app objects on each restart to avoid stale connections
request = HTTPXRequest(proxy=proxy, read_timeout=30, write_timeout=30, connect_timeout=30, pool_timeout=30)
app = (ApplicationBuilder()
.token(mykey.tg_bot_token)
.token(mykeys['tg_bot_token'])
.request(request)
.get_updates_request(request)
.build())