sgclaw: snapshot today's runtime and skill updates

This commit is contained in:
zyl
2026-03-30 15:05:39 +08:00
parent c793bfc6a1
commit f51d6b7659
50 changed files with 3473 additions and 621 deletions

View File

@@ -0,0 +1,219 @@
import json
import subprocess
import textwrap
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
PREPARE_SCRIPT_PATH = (
REPO_ROOT.parent / "skill_lib" / "skills" / "zhihu-write" / "scripts" /
"prepare_article_editor.js"
)
FILL_SCRIPT_PATH = (
REPO_ROOT.parent / "skill_lib" / "skills" / "zhihu-write" / "scripts" /
"fill_article_draft.js"
)
def run_browser_script(script_path: Path, *, args: dict, body_text: str, selectors: dict[str, list[dict]]) -> dict:
node_script = textwrap.dedent(
f"""
import fs from 'node:fs';
import vm from 'node:vm';
const scriptPath = {json.dumps(str(script_path))};
const args = {json.dumps(args, ensure_ascii=False)};
const selectorMap = {json.dumps(selectors, ensure_ascii=False)};
const bodyText = {json.dumps(body_text, ensure_ascii=False)};
const source = fs.readFileSync(scriptPath, 'utf8');
function createNode(spec) {{
const attrs = spec?.attrs || {{}};
const node = {{
tagName: String(spec?.tagName || 'DIV').toUpperCase(),
textContent: String(spec?.textContent ?? ''),
innerText: String(spec?.innerText ?? spec?.textContent ?? ''),
innerHTML: String(spec?.innerHTML ?? spec?.textContent ?? ''),
value: String(spec?.value ?? ''),
children: [],
focused: false,
clicked: false,
appendChild(child) {{
this.children.push(child);
return child;
}},
focus() {{
this.focused = true;
}},
click() {{
this.clicked = true;
}},
dispatchEvent() {{
return true;
}},
getAttribute(name) {{
return Object.prototype.hasOwnProperty.call(attrs, name) ? attrs[name] : null;
}},
querySelector() {{
return null;
}},
querySelectorAll() {{
return [];
}},
getBoundingClientRect() {{
return {{
width: spec?.visible === false ? 0 : 100,
height: spec?.visible === false ? 0 : 20,
}};
}},
}};
return node;
}}
const created = new Map();
function createNodeList(selector) {{
const specs = selectorMap[selector] || [];
return specs.map((spec, index) => {{
const key = `${{selector}}#${{index}}`;
if (!created.has(key)) {{
created.set(key, createNode(spec));
}}
return created.get(key);
}});
}}
const bodyNode = createNode({{ tagName: 'body', textContent: bodyText, innerText: bodyText }});
const context = {{
args,
location: {{ href: 'https://zhuanlan.zhihu.com/write' }},
document: {{
body: bodyNode,
createElement(tagName) {{
return createNode({{ tagName }});
}},
createTextNode(text) {{
return createNode({{ tagName: '#text', textContent: text, innerText: text }});
}},
querySelector(selector) {{
if (selector === 'body') {{
return bodyNode;
}}
return createNodeList(selector)[0] || null;
}},
querySelectorAll(selector) {{
return createNodeList(selector);
}},
}},
Event: class Event {{
constructor(type, init = {{}}) {{
this.type = type;
this.bubbles = !!init.bubbles;
this.composed = !!init.composed;
}}
}},
console,
JSON,
Math,
Number,
Object,
RegExp,
Set,
String,
Array,
Error,
}};
try {{
const result = vm.runInNewContext(`(function(){{\\n${{source}}\\n}})()`, context);
process.stdout.write(JSON.stringify({{ ok: true, result, created: Object.fromEntries(created) }}));
}} catch (error) {{
process.stdout.write(JSON.stringify({{
ok: false,
error: String(error && error.message ? error.message : error),
}}));
process.exitCode = 1;
}}
"""
)
completed = subprocess.run(
["node", "--input-type=module", "-e", node_script],
check=False,
capture_output=True,
text=True,
)
payload = json.loads(completed.stdout)
if completed.returncode != 0:
raise AssertionError(payload["error"])
return payload
class SkillScriptZhihuWriteTest(unittest.TestCase):
def test_prepare_article_editor_accepts_role_textbox_title_and_generic_body_editor(self):
payload = run_browser_script(
PREPARE_SCRIPT_PATH,
args={"desired_mode": "draft"},
body_text="写文章 发布",
selectors={
"[role='textbox'][aria-label*='标题']": [
{
"tagName": "div",
"attrs": {
"role": "textbox",
"aria-label": "标题",
"contenteditable": "true",
},
}
],
"div[contenteditable='true']": [
{
"tagName": "div",
"attrs": {
"contenteditable": "true",
"data-placeholder": "在这里输入正文",
},
}
],
},
)
self.assertEqual(payload["result"]["status"], "editor_ready")
def test_fill_article_draft_accepts_role_textbox_title_and_generic_body_editor(self):
payload = run_browser_script(
FILL_SCRIPT_PATH,
args={
"title": "测试标题",
"body": "第一段\n第二段",
"publish_mode": "false",
},
body_text="写文章 发布",
selectors={
"[role='textbox'][aria-label*='标题']": [
{
"tagName": "div",
"attrs": {
"role": "textbox",
"aria-label": "标题",
"contenteditable": "true",
},
}
],
"div[contenteditable='true']": [
{
"tagName": "div",
"attrs": {
"contenteditable": "true",
"data-placeholder": "在这里输入正文",
},
}
],
},
)
self.assertEqual(payload["result"]["status"], "draft_ready")
if __name__ == "__main__":
unittest.main()