Refactor: restructure directory and clean up redundant scripts

This commit is contained in:
Liang Jiaqing
2026-02-01 08:58:26 +08:00
parent 669095699e
commit 35a1b70208
9 changed files with 109 additions and 85 deletions

View File

@@ -0,0 +1,28 @@
## Global Memory Index (Logic)
[CONSTITUTION]
1. 改我自身源码前必须先问用户;在 ./ 内自由实验可直接做。
2. 要下结论/依赖环境/做操作前:先枚举可用存储(store)与索引;再读取相关条目;未验证不硬断言。
3. 没有用户授权不要直接文本读取或移动密钥/密码文件,能引用即可。或者先请求用户许可。
[STORES]
- global_mem: ../memory/global_mem.txt (Facts, Policy, Prefs)
- memory_dir: ../memory/ (SOPs, Tools)
[ACCESS]
- global_mem: 按 TOPIC 检索索引 → file_read 对应条目。
- memory_dir: ls ../memory/
[TOPICS.GLOBAL_MEM]
# 【引导注释 - 使用后请删除】
# 请模仿以下概括性 Tag 格式记录 global_mem.txt 中的事实:
# - env/network: 网络代理或环境描述 (Global Facts ## [SECTION])
# - auth/platform: 平台账号或认证信息 (Global Facts ## [SECTION])
# - pref/download_dir: 文件存储偏好路径 (Global Facts ## [SECTION])
# - tool/config: 核心工具运行参数 (Global Facts ## [SECTION])
[TOPICS.MEMORY_DIR]
# 【引导注释 - 使用后请删除】
# 请模仿以下格式登记 memory 目录下的独立文件:
# - sop/workflow: 标准操作流程文档 (.md)
# - tool/automation: 自动化执行脚本 (.py)

View File

@@ -0,0 +1,428 @@
// ==UserScript==
// @name ljq_web_driver
// @namespace http://tampermonkey.net/
// @version 0.2
// @description Execute JS via ljq_web_driver
// @require https://code.jquery.com/jquery-3.6.0.min.js
// @author You
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant GM_openInTab
// @grant unsafeWindow
// @connect localhost
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
const log_prefix = "ljq_driver: ";
if (window.self !== window.top) {
console.log(log_prefix + '在iframe中不执行');
return;
}
const wsUrl = 'ws://localhost:18765';
const httpUrl = 'http://localhost:18766/';
function isWebSocketServerAlive(callback) {
GM_xmlhttpRequest({
method: 'GET',
url: 'http://localhost:18765/',
onload: () => callback(true),
onerror: () => callback(false)
});
}
let ws;
let sid = (window.name && window.name.startsWith('ljq_')) ?
window.name : window.sessionStorage.getItem('ljq_driver_sid');
if (!sid) {
sid = `ljq_${Date.now().toString().slice(-2)}${Math.random().toString(36).slice(2, 4)}`;
window.sessionStorage.setItem('ljq_driver_sid', sid);
window.name = sid;
console.log(log_prefix + `创建新会话ID: ${sid}`);
} else {
if (window.name !== sid) window.name = sid;
console.log(log_prefix + `使用现有会话ID: ${sid}`);
}
try {
GM_setValue('new_tab_report', {
url: window.location.href,
sessionId: sid,
ts: Date.now()
});
} catch (e) {}
// 保存会话ID
GM_setValue('sid', sid);
// 获取或创建状态指示器
function getIndicator() {
// 检查现有指示器
let ind = document.getElementById('ljq-ind');
// 删除重复指示器
const dups = document.querySelectorAll('[id="ljq-ind"]');
if (dups.length > 1) {
for (let i = 1; i < dups.length; i++) {
dups[i].remove();
}
ind = dups[0];
}
// 创建新指示器
if (!ind && document.body) {
ind = document.createElement('div');
ind.id = 'ljq-ind';
ind.style.cssText = `
position: fixed;bottom: 10px;
right: 10px;background-color: #f44336;
color: white;padding: 8px 12px;
border-radius: 6px;font-size: 14px;
font-weight: bold;z-index: 9999;
transition: background-color 0.3s;
cursor: pointer;box-shadow: 0 3px 6px rgba(0,0,0,0.25);
`;
ind.innerText = log_prefix + '正在连接...';
ind.addEventListener('click', () => alert(`会话ID: ${sid}\n当前URL: ${location.href}`));
document.body.appendChild(ind);
}
return ind;
}
// 更新状态
function updateStatus(status, msg) {
if (!document.body) return setTimeout(() => updateStatus(status, msg), 100);
const ind = getIndicator();
if (!ind) return;
if (status === 'ok') {
ind.style.backgroundColor = '#4CAF50';
ind.innerText = log_prefix + '连接成功';
} else if (status === 'disc') {
ind.style.backgroundColor = '#f44336';
ind.innerText = log_prefix + '连接断开';
} else if (status === 'conn') {
ind.style.backgroundColor = '#2196F3';
ind.innerText = log_prefix + '正在连接(HTTP)';
} else if (status === 'err') {
ind.style.backgroundColor = '#FF9800';
ind.innerText = log_prefix + `发生错误 (${msg})`;
} else if (status === 'exec') {
ind.style.backgroundColor = '#2196F3';
ind.innerText = log_prefix + '正在执行指令...';
}
}
function handleError(id, error, errorSource) {
console.error(`${errorSource}错误:`, error);
updateStatus('err', error.message);
const errorMessage = {
type: 'error',
id: id,
sessionId: sid,
error: {
name: error.name,
message: error.message,
stack: error.stack,
source: errorSource
}
};
if (typeof ws !== 'undefined' && ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(errorMessage));
} else {
GM_xmlhttpRequest({
method: "POST",
url: httpUrl + "api/result",
headers: {"Content-Type": "application/json"},
data: JSON.stringify(errorMessage),
onload: function(response) {console.log("错误信息已通过HTTP发送", response);},
onerror: function(err) {console.error("发送错误信息失败", err);}
});
}
}
function smartProcessResult(result) {
// 处理 null 和原始类型
if (result === null || result === undefined || typeof result !== 'object') {
return result;
}
// 1. 处理 jQuery 对象 - 强制转换为HTML字符串数组
if (typeof jQuery !== 'undefined' && result instanceof jQuery) {
const elements = [];
for (let i = 0; i < result.length; i++) {
if (result[i] && result[i].nodeType === 1) {
elements.push(result[i].outerHTML);
}
}
return elements; // 始终返回数组
}
// 2. 处理 NodeList 和 HTMLCollection
if (result instanceof NodeList || result instanceof HTMLCollection) {
const elements = [];
for (let i = 0; i < result.length; i++) {
if (result[i] && result[i].nodeType === 1) {
elements.push(result[i].outerHTML);
}
}
return elements;
}
// 3. 处理单个 DOM 元素
if (result.nodeType === 1) {
return result.outerHTML;
}
// 4. 检查是否是具有数字索引和length属性的类数组对象
if (!Array.isArray(result) &&
typeof result === 'object' &&
'length' in result &&
typeof result.length === 'number') {
// 检查第一个元素是否是DOM节点
const firstElement = result[0];
if (firstElement && firstElement.nodeType === 1) {
const elements = [];
const length = Math.min(result.length, 100);
for (let i = 0; i < length; i++) {
const elem = result[i];
if (elem && elem.nodeType === 1) {
elements.push(elem.outerHTML);
}
}
return elements;
}
}
// 5. 处理普通对象和数组 - 使用标准序列化
try {
return JSON.parse(JSON.stringify(result, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (value.nodeType === 1) {
return value.outerHTML;
}
if (value === window || value === document) {
return '[Object]';
}
}
return value;
}));
} catch (e) {
console.error("序列化对象失败:", e);
return `[无法序列化的对象: ${e.message}]`;
}
}
// 防止重复初始化
if (window.ljq_init) return;
window.ljq_init = true;
function connecthttp() {
if (window.use_ws) return;
updateStatus('conn');
GM_xmlhttpRequest({
method: "POST",
url: httpUrl + "api/longpoll",
headers: {"Content-Type": "application/json"},
data: JSON.stringify({
type: 'ready',
url: location.href,
sessionId: sid
}),
onload: function(resp) {
if (resp.status === 200) {
let data = JSON.parse(resp.responseText);
console.log(log_prefix + '接收到数据:', data);
if (data.id === "" && data.ret === "use ws") return;
if (data.id === "") return setTimeout(connecthttp, 100);
const response = executeCode(data);
if (response.error) {
handleError(data.id, response.error, '执行代码');
} else {
GM_xmlhttpRequest({
method: "POST",
url: httpUrl + "api/result",
headers: {"Content-Type": "application/json"},
data: JSON.stringify({
type: 'result',
id: data.id,
sessionId: sid,
result: response.result
})
});
}
} else {
console.error(log_prefix + '请求失败,状态码:', resp.status);
updateStatus('err', '请求失败');
}
setTimeout(connecthttp, 1000);
},
onerror: function(err) {
console.error(log_prefix + '请求错误', err);
updateStatus('err', '请求失败');
setTimeout(connecthttp, 5000);
},
ontimeout: function() {
console.log(log_prefix + '请求超时');
updateStatus('err', '请求超时');
setTimeout(connecthttp, 5000);
}
});
}
function executeCode(data) {
let id = data.id || 'unknown'; // 获取 ID
let result;
if (!data.code) {
console.log('收到非代码执行消息:', data);
return { error: '没有可执行的代码' };
}
updateStatus('exec');
try {
const jsCode = data.code.trim();
const lines = jsCode.split(/\r?\n/).filter(l => l.trim());
const lastLine = lines.length > 0 ? lines[lines.length - 1].trim() : '';
if (lastLine.startsWith('return')) {
// 最后一行包含 return 语句,使用 Function 构造器
result = (new Function(jsCode))();
} else {
try {
result = eval(jsCode);
} catch (e) {
if (isIllegalReturnError(e)) {
result = (new Function(jsCode))();
} else {
throw e;
}
}
}
const processedResult = smartProcessResult(result);
return { result: processedResult };
} catch (execError) {
return { error: execError }; // 返回错误信息
}
}
function isIllegalReturnError(e) {
return e instanceof SyntaxError && (
/Illegal return statement/i.test(e.message) || // Chrome 常见
/return not in function/i.test(e.message) || // Firefox 常见
/Illegal 'return' statement/i.test(e.message) // 兼容旧文案
);
}
function connect() {
ws = new WebSocket(wsUrl);
ws.onopen = function() {
window.use_ws = true;
console.log(log_prefix + '已连接');
updateStatus('ok');
ws.send(JSON.stringify({
type: 'ready',
url: location.href,
sessionId: sid
}));
};
ws.onclose = function() {
console.log(log_prefix + '已断开5秒后重连');
updateStatus('disc');
setTimeout(connect, 5000);
};
ws.onerror = function(err) {
console.error(log_prefix + '连接错误', err);
updateStatus('err', '连接失败');
isWebSocketServerAlive(function (e) { if (e) connecthttp()});
};
ws.onmessage = async function(e) {
try {
let data = JSON.parse(e.data);
let startTime = Date.now();
let newTabs = [];
let checkNewTab = data.auto_switch_newtab === true;
GM_setValue('new_tab_report', null);
const response = executeCode(data);
if (response.error) {
handleError(data.id, response.error, '执行代码');
} else {
if (checkNewTab) {
for (let i = 0; i < 10; i++) {
await new Promise(r => setTimeout(r, 150));
let latestReport = GM_getValue('new_tab_report');
if (latestReport && latestReport.ts >= startTime) {
console.log(`%c[Detected] 轮询第 ${i+1} 次抓到新标签!`, "color: green");
newTabs.push(latestReport);
break;
}
}
}
updateStatus('ok');
ws.send(JSON.stringify({
type: 'result',
id: data.id,
sessionId: sid,
result: response.result,
newTabs: newTabs
}));
}
} catch (parseError) {
handleError('unknown', parseError, '解析消息');
}
};
}
// 初始化
function init() {
if (document.body) {
getIndicator();
connect();
} else {
setTimeout(init, 50);
}
}
// 监控DOM变化
const observer = new MutationObserver(() => getIndicator());
if (document.readyState !== 'loading') {
init();
observer.observe(document.body, { childList: true, subtree: true });
} else {
document.addEventListener('DOMContentLoaded', () => {
init();
observer.observe(document.body, { childList: true, subtree: true });
});
}
// 清理
window.addEventListener('beforeunload', () => {
observer.disconnect();
if (ws && ws.readyState === WebSocket.OPEN) {
ws.close();
}
});
})();

137
assets/make_prompts.py Normal file
View File

@@ -0,0 +1,137 @@
import sys, os, re
import pyperclip
import json, time
from pathlib import Path
import subprocess
import tempfile
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from sidercall import SiderLLMSession, LLMSession, ToolClient
ask = SiderLLMSession().ask
def generate_tool_schema():
"""
通过代码内省,将 Handler 的逻辑映射为高语义的工具描述。
"""
with open('../ga.py', 'r', encoding='utf-8') as f:
ga_code = f.read()
# 极简且具备高度概括能力的元 Prompt
meta_prompt = f"""
# Role
你是一个具备深度推理能力的 AI 系统架构师。你将通过阅读 `GenericAgentHandler` 源码,构建其对应的工具能力矩阵。
# Task
分析下方的源码,并输出 OpenAI Tool Schema。在输出 JSON 之前你必须进行内部思考Thinking Process
# Thinking Process Requirements
在 `<thinking>` 标签中,请按顺序分析:
1. **核心工具链识别**:识别所有 `do_xxx` 方法,并分析它们依赖的底层 Utility 函数。
2. **内容溯源审计**:重点分析哪些工具是从 `response.content` 提取核心逻辑(如代码块)的。对于这些工具,确认在 Schema 参数中排除掉对应的字段。
3. **调用策略推导**:分析工具间的协作关系(例如 `file_read` 如何为 `file_patch` 提供定位)。
4. **兜底逻辑确认**:明确某些特殊万能工具在系统中的保底角色,快速工具无法执行的操作由保底工具执行,但正常应优先使用方便的工具。
5. **注释审阅**:结合函数注释,理解每个工具的使用限制,其中的重要信息务必反映在工具描述中(如长度限制等)。
注释中的重要信息务必反映在工具描述中。
注释中的重要信息务必反映在工具描述中。
# Tool Schema Formatting Rules
- **参数对齐**:仅包含 `do_xxx` 方法中通过 `args.get()` 显式获取的参数。
- **高引导性描述**:描述应包含“何时调用”以及“如何根据反馈修正”,需要注意函数的注释事项。
- **输出格式**:先输出 `<thinking>` 块,然后输出 ```json 块。
# Source Code
{ga_code}
# Output
请开始思考并生成:
"""
# 假设 ask 是你已经封装好的 LLM 调用接口
raw_response = ask(meta_prompt, model="gemini-3.0-flash")
print(raw_response)
# --- 健壮的 JSON 解析逻辑 ---
try:
# 1. 清除 Markdown 围栏
clean_json = raw_response.strip()
if clean_json.startswith("```"):
# 兼容 ```json 和 ```
clean_json = re.sub(r'^```(?:json)?\s*', '', clean_json)
clean_json = re.sub(r'\s*```$', '', clean_json)
# 2. 移除可能的非 JSON 前导/后缀文字(如果有的话)
start_idx = clean_json.find('[')
end_idx = clean_json.rfind(']') + 1
if start_idx != -1 and end_idx != -1:
clean_json = clean_json[start_idx:end_idx]
final_schema = json.loads(clean_json)
if final_schema:
with open('tools_schema.json', 'w', encoding='utf-8') as f:
json.dump(final_schema, f, indent=2, ensure_ascii=False)
print("✅ 成功从代码内省生成 Schema 并持久化。")
return final_schema
except Exception as e:
print(f"❌ 解析 Schema 失败: {e}\n原始响应: {raw_response}")
return None
def make_system_prompt(ga_code_path='../ga.py'):
with open(ga_code_path, 'r', encoding='utf-8') as f:
ga_code = f.read()
# 这个元 Prompt 的目标是生成“世界观”而非“说明书”
meta_prompt = f"""
# Role
你是一个 AI 架构师。请阅读下方的工具库源码,为 Agent 生成一份【系统级认知指令 (System Prompt)】。
# Task
基于代码逻辑,定义 Agent 的“能力边界”和“行动协议”。
# Requirements (Crucial)
1. **打破预训练偏见**:针对模型常说的“我只是 AI不能操作网页/文件”进行修正。明确告诉它:你现在拥有物理操作权限,工具设计保证了所有权限做所有事情。
2. **避开冗余**:不要重复 Tool Schema 里的参数细节。
3. **能力边界定义**
- 网页操作:它不是通过“想象”上网,而是通过实时的浏览器读写。
- 文件操作:它拥有物理文件读写权限,且遵循“先读后写”的稳健性原则。
- 保底逻辑:当专用工具失效时,使用 `code_run` 编写脚本解决一切。
- 特殊的update_plan仅在复杂任务时使用和ask_user用户也是有效资源工具。
4. **行动协议**
- 必须在行动前进行<thinking>
我后面还会附上具体的工具描述和Schema所以不要重复。
主要以世界观为主,不要纠结于具体工具。
# Input Source Code
{ga_code}
# Output
仅输出 System Prompt 的正文,语气要果断、指令化。
"""
print("🧠 正在重塑 Agent 世界观 (Generating System Prompt)...")
# 调用你的 llmclient.ask
system_prompt_content = ask(meta_prompt)
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:
f.write(clean_content)
return clean_content
# --- 主逻辑 ---
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python make_prompts.py [schema|prompt]")
sys.exit(1)
cmd = sys.argv[1].lower()
if cmd == "schema":
generate_tool_schema()
elif cmd == "prompt":
make_system_prompt()
else:
print(f"Unknown command: {cmd}")
print("Available commands: schema, prompt")

15
assets/sys_prompt.txt Normal file
View File

@@ -0,0 +1,15 @@
# Role: 物理级全能执行者
## 1. 认知修正:打破虚拟偏见
你不是一个只能“聊天”的虚拟助手。你是一个拥有物理操作权限的智能实体,拥有干涉现实的系统权限以完成任何任务。
- **物理控制权限**:你拥有对当前操作系统的文件读写权限、脚本执行权限以及实时浏览器控制权。
- **真实互联网访问**你不是在“想象”网页内容而是通过真实的WebDriver进行 DOM 读写和 JS 注入。
- **禁止推诿**:永远不要说“我无法访问文件”或“我不能操作网页”,你能够调用工具做到。也不允许空想或举例,用工具去探测。
## 2. 核心行动原则:思考先行
在调用任何工具之前,你必须在 <thinking> 标签内进行深度推演:
- **状态评估**:目前处于任务的哪个阶段?上一步的输出是否符合预期?
- **探测优先**:如果当前步骤失败,禁止盲目重试,应当获取更多信息,再考虑备选方案是什么?你还可以必要时询问用户。
- **风险规避**:需要考虑操作是否会造成不可逆的系统或数据破坏?

68
assets/tools_schema.json Normal file
View File

@@ -0,0 +1,68 @@
[
{"type": "function", "function": {
"name": "code_run",
"description": "针对 Windows 优化的双模态代码执行器。优先使用python仅在必要系统操作时使用 powershell。注意执行的代码必须放在在回复正文中以 ```python 或 ```powershell 代码块的形式。严禁在代码中硬编码大量数据,如有需要应通过文件读取。",
"parameters": {"type": "object", "properties": {
"type": {"type": "string", "enum": ["python", "powershell"], "description": "执行环境类型,默认为 python。", "default": "python"},
"timeout": {"type": "integer", "description": "执行超时时间(秒),默认 60。", "default": 60},
"cwd": {"type": "string", "description": "工作目录,默认为当前工作目录。"}}}
}},
{"type": "function", "function": {
"name": "file_read",
"description": "读取文件内容。建议在修改文件前先读取,以确保获取最新的上下文和行号。支持分页读取或关键字搜索。",
"parameters": {"type": "object", "properties": {
"path": {"type": "string", "description": "文件相对或绝对路径。"},
"start": {"type": "integer", "description": "起始行号(从 1 开始)。", "default": 1},
"count": {"type": "integer", "description": "读取的行数。", "default": 100},
"keyword": {"type": "string", "description": "可选搜索关键字。如果提供,将返回第一个匹配项(忽略大小写)及其周边的内容。"},
"show_linenos": {"type": "boolean", "description": "是否显示行号,建议开启以辅助 file_patch 定位。", "default": true}}, "required": ["path"]}
}},
{"type": "function", "function": {
"name": "file_patch",
"description": "精细化局部文件修改。在文件中寻找唯一的 old_content 块并替换为 new_content。要求 old_content 必须在文件中唯一存在,且空格、缩进、换行必须与原文件完全一致。如果匹配失败,请使用 file_read 重新确认文件内容。",
"parameters": {"type": "object", "properties": {
"path": {"type": "string", "description": "文件路径。"},
"old_content": {"type": "string", "description": "文件中需要被替换的原始文本块(需确保唯一性)。"},
"new_content": {"type": "string", "description": "替换后的新文本内容。"}}, "required": ["path", "old_content", "new_content"]}
}},
{"type": "function", "function": {
"name": "file_write",
"description": "用于文件的新建、全量覆盖或追加写入。对于精细的代码修改,应优先使用 file_patch。注意要写入的内容必须放在回复正文的 <file_content> 标签或代码块中。",
"parameters": {"type": "object", "properties": {
"path": {"type": "string", "description": "文件路径。"},
"mode": {"type": "string", "enum": ["overwrite", "append"], "description": "写入模式overwrite覆盖默认或 append追加。", "default": "overwrite"}}, "required": ["path"]}
}},
{"type": "function", "function": {
"name": "web_scan",
"description": "获取当前网页的清洗后内容,并列出所有已打开的标签页。支持切换标签页。在长页面中,可以使用 focus_item 进行语义过滤以提取关键信息。",
"parameters": {"type": "object", "properties": {
"focus_item": {"type": "string", "description": "语义过滤指令,用于在长列表中优先保留与该关键词相关的项。"},
"switch_tab_id": {"type": "string", "description": "可选的标签页 ID。如果提供系统将在扫描前切换到该标签页。"}}}
}},
{"type": "function", "function": {
"name": "web_execute_js",
"description": "万能网页操控工具。通过执行 JavaScript 脚本实现对浏览器的完全控制(如点击、滚动、提取特定数据)。这是 Web 场景下的首选工具。执行结果可选择保存到本地文件进行后续分析。",
"parameters": {"type": "object", "properties": {
"script": {"type": "string", "description": "要执行的 JavaScript 代码。"},
"save_to_file": {"type": "string", "description": "可选。将 JS 执行结果js_return保存到的文件路径。注意该功能不支持 await 等异步结果。"}}, "required": ["script"]}
}},
{"type": "function", "function": {
"name": "update_plan",
"description": "更新任务的宏观计划和当前战略重心。仅在初始拆解多步任务或发生重大方案调整时使用。禁止用于记录细微调试步骤或纠错。",
"parameters": {"type": "object", "properties": {
"plan": {"type": "string", "description": "完整的宏观任务路线图。"},
"focus": {"type": "string", "description": "当前阶段的工作重点。"}}}
}},
{"type": "function", "function": {
"name": "ask_user",
"description": "当需要用户决策、提供额外信息或遇到无法自动解决的阻碍时,调用此工具中断任务并提问。",
"parameters": {"type": "object", "properties": {
"question": {"type": "string", "description": "向用户提出的明确问题。"},
"candidates": {"type": "array", "items": {"type": "string"}, "description": "提供给用户的可选快捷选项列表。"}}, "required": ["question"]}
}},
{"type": "function", "function": {
"name": "distill_good_memory",
"description": "当模型认为当前任务执行完美,且有具有长期价值的环境事实或用户偏好需要提炼并存入全局记忆时,调用此工具。注意:此工具无参数,调用即代表触发记忆提炼流程。",
"parameters": {"type": "object", "properties": {}}}
}
]