Compare commits
28
Commits
v0.2.1
...
DinTalClaw
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5eac3c1027 | ||
|
|
61d07a16a6 | ||
|
|
7ab47af8e8 | ||
|
|
fd9f172588 | ||
|
|
2156bc6fc6 | ||
|
|
959cf6a5a6 | ||
|
|
cccbecb974 | ||
|
|
2272f255f2 | ||
|
|
524944dfc8 | ||
|
|
c6af92e612 | ||
|
|
ffd08bcd21 | ||
|
|
19d4e65133 | ||
|
|
ed75d103f5 | ||
|
|
313f8a28f0 | ||
|
|
3f8407a853 | ||
|
|
ffe1f3c4c0 | ||
|
|
0ab90af250 | ||
|
|
dcf65d72c7 | ||
|
|
9219042a8f | ||
|
|
5c3193f326 | ||
|
|
d010ab1995 | ||
|
|
066a2995e4 | ||
|
|
82991c4a69 | ||
|
|
4ba1dcc7a2 | ||
|
|
2655d79acb | ||
|
|
f27c067b1d | ||
|
|
75ddf8fd1d | ||
|
|
392747798a |
No files matched your search
+9
-1
@@ -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.*
|
||||
@@ -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
@@ -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
@@ -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]')
|
||||
@@ -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; // 异步响应
|
||||
}
|
||||
});
|
||||
@@ -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});
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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修改(除非新建)。
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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": {}}}
|
||||
}
|
||||
]
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -41,3 +41,4 @@ ljqCtrl.Click(px, py)
|
||||
- **物理验证**:模拟操作前必须确保窗口已通过 `activate()` 置于前台。
|
||||
- **偏移量**:所有的相对偏移像素值(如“向右移动 10 像素”)同样需要除以 `dpi_scale`。
|
||||
- **坐标对齐**: 物理坐标 = 截图坐标;ljqCtrl 自动处理 DPI 换算,禁止手动重复计算。
|
||||
- **⚠️ 窗口坐标转换陷阱**:使用 `win32gui.GetWindowRect(hwnd)` 获取的矩形包含标题栏和边框,而截图内容是客户区。点击截图内元素时,必须用 `win32gui.ClientToScreen(hwnd, (0, 0))` 获取客户区原点的屏幕坐标,再加上截图内坐标。禁止直接用 GetWindowRect 左上角 + 截图坐标。
|
||||
@@ -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 只写关键词/名称,禁搬细节。
|
||||
|
||||
---
|
||||
## 信息分类快速决策树
|
||||
```
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# reflect/autonomous.py
|
||||
INTERVAL = 1800
|
||||
ONCE = False
|
||||
|
||||
def check():
|
||||
return "[AUTO]🤖 用户已经离开超过30分钟,作为自主智能体,请阅读自动化sop,执行自动任务。"
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in new issue
Block a user