codebee 0.1.102 → 0.1.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -21
  3. package/README.md +4 -8
  4. package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +88 -0
  5. package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +126 -0
  6. package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
  7. package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +210 -0
  8. package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
  9. package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
  10. package/app/.playwright-cli/run-detail-after.png +0 -0
  11. package/app/core/acceptance.py +100 -100
  12. package/app/core/acp_client.py +457 -457
  13. package/app/core/aiflavor.py +197 -197
  14. package/app/core/analytics.py +38 -38
  15. package/app/core/artifacts.py +148 -148
  16. package/app/core/assets.py +110 -110
  17. package/app/core/attachments.py +623 -623
  18. package/app/core/audit.py +30 -30
  19. package/app/core/backend_profiles.py +188 -188
  20. package/app/core/backup.py +470 -470
  21. package/app/core/beekeeper.py +183 -183
  22. package/app/core/benchstore.py +102 -102
  23. package/app/core/bookmeta.py +1036 -1036
  24. package/app/core/bookmeta_catalog.py +180 -180
  25. package/app/core/branching.py +137 -137
  26. package/app/core/capability.py +144 -144
  27. package/app/core/chaptersafe.py +32 -32
  28. package/app/core/cleanup.py +303 -303
  29. package/app/core/command_auth.py +302 -302
  30. package/app/core/compaction.py +201 -201
  31. package/app/core/contracts.py +255 -255
  32. package/app/core/covergen.py +356 -356
  33. package/app/core/defectretro.py +43 -43
  34. package/app/core/diagnostics.py +114 -114
  35. package/app/core/dispatch.py +1 -1
  36. package/app/core/dispatch_log.py +128 -128
  37. package/app/core/env_scrub.py +85 -85
  38. package/app/core/errorlog.py +137 -137
  39. package/app/core/eval_matrix.py +91 -91
  40. package/app/core/evaluation.py +174 -174
  41. package/app/core/failure_notify.py +290 -290
  42. package/app/core/flow_graph.py +33 -33
  43. package/app/core/goal_service.py +167 -167
  44. package/app/core/health.py +389 -389
  45. package/app/core/history.py +38 -38
  46. package/app/core/hooks.py +162 -162
  47. package/app/core/knowledge_pipeline.py +86 -86
  48. package/app/core/manager.py +2385 -2385
  49. package/app/core/mocks.py +64 -64
  50. package/app/core/notify.py +201 -201
  51. package/app/core/novel_quality.py +181 -181
  52. package/app/core/openapi.py +217 -217
  53. package/app/core/operations.py +228 -228
  54. package/app/core/paihang.py +145 -145
  55. package/app/core/pipeline.py +2 -2
  56. package/app/core/portguard.py +111 -111
  57. package/app/core/presence.py +77 -77
  58. package/app/core/preview.py +293 -293
  59. package/app/core/project_memory.py +206 -206
  60. package/app/core/prompt_guard.py +78 -78
  61. package/app/core/publish/__init__.py +7 -7
  62. package/app/core/publish/auto.py +659 -659
  63. package/app/core/publish/chaprows.py +138 -138
  64. package/app/core/publish/flows-fanqie-calibrated.json +442 -442
  65. package/app/core/publish/manager.py +1185 -1185
  66. package/app/core/publish/qimao.py +193 -193
  67. package/app/core/publish/volumes.py +108 -108
  68. package/app/core/publish/ws.py +139 -139
  69. package/app/core/quality_gate.py +201 -201
  70. package/app/core/redact.py +38 -38
  71. package/app/core/repeat_guard.py +129 -129
  72. package/app/core/retrieval.py +137 -137
  73. package/app/core/sandbox_runtime.py +95 -95
  74. package/app/core/selfupdate.py +384 -384
  75. package/app/core/session_log.py +161 -161
  76. package/app/core/sessions.py +312 -312
  77. package/app/core/settings.py +215 -215
  78. package/app/core/settings_schema.py +282 -282
  79. package/app/core/share_page.py +114 -114
  80. package/app/core/skill_scan.py +139 -139
  81. package/app/core/skillpacks/market/character-bible.md +66 -66
  82. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  83. package/app/core/skillpacks/market/git-workflow.md +57 -57
  84. package/app/core/skillpacks/market/release-notes.md +72 -72
  85. package/app/core/skillpacks/market/weekly-report.md +71 -71
  86. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  87. package/app/core/step_runner.py +132 -132
  88. package/app/core/story_tracking.py +275 -275
  89. package/app/core/task_compile.py +199 -199
  90. package/app/core/telemetry.py +292 -292
  91. package/app/core/tlsctx.py +64 -64
  92. package/app/core/token_meter.py +147 -147
  93. package/app/core/tool_outputs.py +107 -107
  94. package/app/core/tracing.py +207 -207
  95. package/app/core/upstream.py +41 -41
  96. package/app/core/usage.py +1038 -1038
  97. package/app/core/volumes.py +389 -389
  98. package/app/core/webhooks.py +89 -89
  99. package/app/core/wxdigest.py +865 -865
  100. package/app/core/zentao.py +2692 -2692
  101. package/app/mcp_server.py +244 -244
  102. package/app/output/_createbook_body.json +1 -0
  103. package/app/output/_probe_fq_click.py +50 -0
  104. package/app/output/_probe_fq_form.py +42 -0
  105. package/app/output/_probe_fq_modal.py +55 -0
  106. package/app/output/_probe_fq_points.py +71 -0
  107. package/app/output/_probe_fq_submit.py +34 -0
  108. package/app/output/_probe_fq_watch.py +62 -0
  109. package/app/output/workbench-clean-preview.jpg +0 -0
  110. package/app/output/workbench-reference-render.png +0 -0
  111. package/app/output/workbench-shell.png +0 -0
  112. package/app/pick_dialog.py +110 -110
  113. package/app/ui/app.js +2 -3
  114. package/app/ui/artdiff.js +74 -74
  115. package/app/ui/i18n.js +0 -1
  116. package/app/ui/icons/bee.svg +79 -79
  117. package/app/ui/manifest.json +16 -16
  118. package/app/ui/qrcode.js +2297 -2297
  119. package/bin/tutti.js +147 -147
  120. package/package.json +1 -1
  121. package/tools/wx_reader.py +261 -261
@@ -0,0 +1,55 @@
1
+ # -*- coding: utf-8 -*-
2
+ """番茄建书分类弹层实地探测:复现点击序列 → dump 弹层真实结构。只读不提交。"""
3
+ import sys
4
+ import time
5
+ import json
6
+
7
+ sys.path.insert(0, r"E:\GoOut\MultiAgentOrchestration")
8
+ from app.core.publish import manager # noqa: E402
9
+
10
+ b, page = manager._open_page("fanqie")
11
+ # 硬刷新拿干净表单(同 URL 导航 SPA 不重置,上次建书残留会干扰)
12
+ page.navigate("https://fanqienovel.com/main/writer/create", timeout=30)
13
+ page.call("() => location.reload()")
14
+ time.sleep(5)
15
+
16
+ CLICK_JS = """(t) => {
17
+ const els = [...document.querySelectorAll('span,div,button,label')]
18
+ .filter(e => e.offsetParent && e.textContent.trim() === t);
19
+ if (!els.length) return {ok: false, why: 'no ' + t};
20
+ els[els.length - 1].click();
21
+ return {ok: true, n: els.length};
22
+ }"""
23
+
24
+ for text in ["连载模式", "男频"]:
25
+ r = page.call(CLICK_JS, [text])
26
+ print(text, "->", r)
27
+ time.sleep(1.5)
28
+
29
+ time.sleep(1)
30
+ r = page.call("""() => {
31
+ const els = [...document.querySelectorAll('span,div,button,label')]
32
+ .filter(e => e.offsetParent && e.textContent.includes('请选择作品标签'));
33
+ if (!els.length) return {ok: false, why: 'no tag entry'};
34
+ els[els.length - 1].click();
35
+ return {ok: true, n: els.length};
36
+ }""")
37
+ print("请选择作品标签 ->", r)
38
+ time.sleep(3)
39
+
40
+ DUMP_JS = """() => {
41
+ const cm = document.querySelector('.category-modal');
42
+ const out = {category_modal_exists: !!cm, cm_class: cm ? String(cm.className) : ''};
43
+ const dialogs = [...document.querySelectorAll(
44
+ '[class*=modal],[class*=dialog],[class*=popover],[class*=picker],[class*=drawer],[class*=popup]')]
45
+ .filter(e => e.offsetParent).slice(0, 8);
46
+ out.dialogs = dialogs.map(d => ({
47
+ cls: String(d.className).slice(0, 100),
48
+ text: (d.innerText || '').slice(0, 500).split('\\n').join('|')
49
+ }));
50
+ out.has_dushi = document.body.innerText.indexOf('都市脑洞') >= 0;
51
+ out.body_snip = document.body.innerText.slice(0, 600).split('\\n').join('|');
52
+ return out;
53
+ }"""
54
+ info = page.call(DUMP_JS)
55
+ print(json.dumps(info, ensure_ascii=False, indent=1))
@@ -0,0 +1,71 @@
1
+ # -*- coding: utf-8 -*-
2
+ """逐点试探「请选择作品标签」:不同落点/事件序列,找出能打开分类弹层的精确触发方式。只读。"""
3
+ import sys
4
+ import time
5
+ import json
6
+
7
+ sys.path.insert(0, r"E:\GoOut\MultiAgentOrchestration")
8
+ from app.core.publish import manager # noqa: E402
9
+
10
+ b, page = manager._open_page("fanqie")
11
+ page.navigate("https://fanqienovel.com/main/writer/create", timeout=30)
12
+ page.call("() => location.reload()")
13
+ time.sleep(5)
14
+
15
+ # 前置:先把签约模式/目标读者点上(复现产品完整前置链)
16
+ for text in ["连载模式", "男频"]:
17
+ r = page.real_click_text(text, "label.arco-radio,span.arco-radio,[class*=serial-radio]",
18
+ y_max=430)
19
+ print("前置点击", text, "->", r)
20
+ time.sleep(1.0)
21
+
22
+ MODAL_JS = "() => !!document.querySelector('.category-modal')"
23
+
24
+ # 找到 select-row 及其内部结构
25
+ r = page.call("""() => {
26
+ const row = [...document.querySelectorAll('.select-row')]
27
+ .find(e => (e.innerText || '').includes('请选择作品标签'));
28
+ if (!row) return {ok: false};
29
+ const inner = [...row.querySelectorAll('*')].slice(0, 8).map(e => {
30
+ const rc = e.getBoundingClientRect();
31
+ return {tag: e.tagName, cls: String(e.className).slice(0, 50),
32
+ x: Math.round(rc.x + rc.width / 2), y: Math.round(rc.y + rc.height / 2)};
33
+ });
34
+ const rc = row.getBoundingClientRect();
35
+ return {ok: true, row: {x: Math.round(rc.x + rc.width / 2), y: Math.round(rc.y + rc.height / 2)},
36
+ inner: inner};
37
+ }""")
38
+ print("select-row 结构:", json.dumps(r, ensure_ascii=False))
39
+ if not r.get("ok"):
40
+ sys.exit("select-row 没找到")
41
+
42
+ targets = [r["row"]] + r["inner"]
43
+ for t in targets:
44
+ x, y = t["x"], t["y"]
45
+ page.send("Input.dispatchMouseEvent",
46
+ {"type": "mousePressed", "x": x, "y": y, "button": "left", "clickCount": 1},
47
+ timeout=8.0)
48
+ page.send("Input.dispatchMouseEvent",
49
+ {"type": "mouseReleased", "x": x, "y": y, "button": "left", "clickCount": 1},
50
+ timeout=8.0)
51
+ time.sleep(1.8)
52
+ opened = page.call(MODAL_JS)
53
+ print("点 %s(%s) @(%d,%d) -> 弹层: %s" % (t.get("tag"), t.get("cls", "")[:30], x, y, opened))
54
+ if opened:
55
+ print(">>> 弹层开了!落点:", json.dumps(t, ensure_ascii=False))
56
+ # dump 弹层内主分类区
57
+ info = page.call("""() => {
58
+ const cm = document.querySelector('.category-modal');
59
+ return {cls: String(cm.className).slice(0, 80),
60
+ txt: (cm.innerText || '').slice(0, 300).split('\\n').join('|')};
61
+ }""")
62
+ print(json.dumps(info, ensure_ascii=False, indent=1))
63
+ break
64
+ # 收起可能的意外浮层,回干净状态再试下一个点
65
+ page.send("Input.dispatchMouseEvent",
66
+ {"type": "mousePressed", "x": 200, "y": 700, "button": "left", "clickCount": 1},
67
+ timeout=8.0)
68
+ page.send("Input.dispatchMouseEvent",
69
+ {"type": "mouseReleased", "x": 200, "y": 700, "button": "left", "clickCount": 1},
70
+ timeout=8.0)
71
+ time.sleep(0.8)
@@ -0,0 +1,34 @@
1
+ # -*- coding: utf-8 -*-
2
+ """实地补点「立即创建」并全程盯提交反应(弹窗/跳转)。真实提交,已获用户批准。"""
3
+ import sys
4
+ import time
5
+ import json
6
+
7
+ sys.path.insert(0, r"E:\GoOut\MultiAgentOrchestration")
8
+ from app.core.publish import manager # noqa: E402
9
+
10
+ b, page = manager._open_page("fanqie")
11
+ print("url before:", page.url())
12
+
13
+ r = page.real_click_text("立即创建", "button", contains=False)
14
+ print("点「立即创建」->", r)
15
+
16
+ for i in range(12):
17
+ time.sleep(2.5)
18
+ url = str(page.url() or "")
19
+ st = page.call("""() => {
20
+ const dlg = [...document.querySelectorAll('.arco-modal,[class*=dialog],[class*=modal]')]
21
+ .filter(e => e.offsetParent && (e.innerText || '').trim())
22
+ .slice(0, 3)
23
+ .map(e => ({cls: String(e.className).slice(0, 50),
24
+ txt: (e.innerText || '').slice(0, 200).split('\\n').join('|')}));
25
+ return {dlg: dlg,
26
+ toast: ([...document.querySelectorAll('[class*=message],[class*=toast]')]
27
+ .filter(e => e.offsetParent).map(e => (e.innerText || '').slice(0, 80)) )};
28
+ }""")
29
+ print("[%02d] url=%s" % (i, url[:90]))
30
+ if st.get("dlg") or st.get("toast"):
31
+ print(" 弹窗/提示:", json.dumps(st, ensure_ascii=False)[:400])
32
+ if "book-info" in url or "chapter" in url:
33
+ print(">>> 建书成功跳转!")
34
+ break
@@ -0,0 +1,62 @@
1
+ # -*- coding: utf-8 -*-
2
+ """建书全程盯梢:触发建书线程的同时高频 dump 页面弹窗/toast/URL,抓提交瞬间反应。"""
3
+ import sys
4
+ import time
5
+ import json
6
+ import threading
7
+
8
+ sys.path.insert(0, r"E:\GoOut\MultiAgentOrchestration")
9
+
10
+ BODY = r"E:\GoOut\MultiAgentOrchestration\app\output\_createbook_body.json"
11
+
12
+ def trigger():
13
+ import http.client
14
+ body = open(BODY, "rb").read()
15
+ conn = http.client.HTTPConnection("127.0.0.1", 8765, timeout=30)
16
+ conn.request("POST", "/api/control",
17
+ json.dumps({"action": "acquire", "force": True}).encode(),
18
+ {"Content-Type": "application/json", "X-CodeBee-Client": "cli-maint-0930"})
19
+ conn.getresponse().read()
20
+ conn.request("POST", "/api/publish/task/t-20260924-215431-0715/create-book",
21
+ body, {"Content-Type": "application/json",
22
+ "X-CodeBee-Client": "cli-maint-0930"})
23
+ print("trigger:", conn.getresponse().read()[:120])
24
+ conn.request("POST", "/api/control",
25
+ json.dumps({"action": "release"}).encode(),
26
+ {"Content-Type": "application/json", "X-CodeBee-Client": "cli-maint-0930"})
27
+ conn.getresponse().read()
28
+
29
+ threading.Thread(target=trigger, daemon=True).start()
30
+ time.sleep(2)
31
+
32
+ from app.core.publish import manager # noqa: E402
33
+
34
+ last = ""
35
+ for i in range(75):
36
+ try:
37
+ b, page = manager._open_page("fanqie")
38
+ url = str(page.url() or "")
39
+ st = page.call("""() => {
40
+ const grab = sel => [...document.querySelectorAll(sel)]
41
+ .filter(e => e.offsetParent && (e.innerText || '').trim())
42
+ .map(e => (e.innerText || '').trim().slice(0, 100));
43
+ return {
44
+ dlg: grab('.arco-modal'),
45
+ toast: grab('[class*=message],[class*=toast],[class*=notification]'),
46
+ btn: grab('button').filter(t => t.length < 12).slice(0, 10)
47
+ };
48
+ }""") or {}
49
+ sig = json.dumps([url, st.get("dlg"), st.get("toast")], ensure_ascii=False)
50
+ if sig != last:
51
+ last = sig
52
+ print("[%03d] %s" % (i, url[:80]))
53
+ if st.get("dlg"):
54
+ print(" 弹窗:", json.dumps(st["dlg"], ensure_ascii=False)[:300])
55
+ if st.get("toast"):
56
+ print(" toast:", json.dumps(st["toast"], ensure_ascii=False)[:300])
57
+ if "book-info" in url:
58
+ print(">>> 跳转 book-info!")
59
+ break
60
+ except Exception as e:
61
+ print("[%03d] watch err: %s" % (i, str(e)[:80]))
62
+ time.sleep(2)
Binary file
@@ -1,110 +1,110 @@
1
- #!/usr/bin/env python3
2
- """原生「选择文件夹」对话框(main.py 的 /api/pick_folder 用)。
3
-
4
- Tk 必须活在自家进程的主线程里:HTTP 请求线程里建 root 在 macOS 上会崩,
5
- Windows 上反复建/销毁也不稳,所以隔离成独立进程。ask_directory() 是父端
6
- 入口,把本文件拉起为子进程,请求参数(JSON:initial 起始目录 / title 标题)
7
- 走 stdin,选中目录以 JSON({"path": ...})走 stdout,用户取消回空串;
8
- 对话框部分是文件尾的 main()。任何一步失败都以非零码退出,父端据此回
9
- fallback=true 让前端回落网页目录弹框。"""
10
- import json
11
- import os
12
- import subprocess
13
- import sys
14
- from pathlib import Path
15
-
16
- # 非 Windows 置 0:POSIX 的 Popen 对非零 creationflags 抛 ValueError(manager 同款守卫)
17
- CREATE_NO_WINDOW = 0x08000000 if os.name == "nt" else 0
18
- _SELF = str(Path(__file__).resolve())
19
-
20
-
21
- def ask_directory(initial="", title="选择文件夹"):
22
- """父端入口:拉起子进程弹原生对话框,返回 (path, error, fallback)。
23
-
24
- 用户取消时 path 为空串;子进程起不来(机器无 tkinter 等)时 error
25
- 非空且 fallback=True,调用方原样转给前端回落网页目录弹框。"""
26
- if not Path(__file__).exists():
27
- return "", "pick_dialog.py 缺失", True
28
- req = json.dumps({"initial": initial, "title": title}).encode("utf-8")
29
- env = dict(os.environ, PYTHONIOENCODING="utf-8")
30
- try:
31
- if os.name == "nt":
32
- cp = subprocess.run([sys.executable, _SELF], input=req,
33
- capture_output=True, timeout=600, env=env,
34
- creationflags=CREATE_NO_WINDOW)
35
- else:
36
- cp = subprocess.run([sys.executable, _SELF], input=req,
37
- capture_output=True, timeout=600, env=env)
38
- except Exception as e:
39
- return "", str(e), True
40
- if cp.returncode != 0:
41
- err = (cp.stderr or b"").decode("utf-8", "replace").strip().splitlines()
42
- return "", (err[-1] if err else "native picker unavailable"), True
43
- try:
44
- data = json.loads((cp.stdout or b"").decode("utf-8", "replace") or "{}")
45
- except Exception:
46
- return "", "对话框输出无法解析", True
47
- return str(data.get("path") or ""), "", False
48
-
49
-
50
- def ask_file(initial="", title="选择文件"):
51
- """父端入口:拉起子进程弹原生「选择文件」对话框(导入备份包用)。
52
- 返回 (path, error, fallback),语义与 ask_directory 一致。"""
53
- if not Path(__file__).exists():
54
- return "", "pick_dialog.py 缺失", True
55
- req = json.dumps({"mode": "file", "initial": initial, "title": title}).encode("utf-8")
56
- env = dict(os.environ, PYTHONIOENCODING="utf-8")
57
- try:
58
- if os.name == "nt":
59
- cp = subprocess.run([sys.executable, _SELF], input=req,
60
- capture_output=True, timeout=600, env=env,
61
- creationflags=CREATE_NO_WINDOW)
62
- else:
63
- cp = subprocess.run([sys.executable, _SELF], input=req,
64
- capture_output=True, timeout=600, env=env)
65
- except Exception as e:
66
- return "", str(e), True
67
- if cp.returncode != 0:
68
- err = (cp.stderr or b"").decode("utf-8", "replace").strip().splitlines()
69
- return "", (err[-1] if err else "native picker unavailable"), True
70
- try:
71
- data = json.loads((cp.stdout or b"").decode("utf-8", "replace") or "{}")
72
- except Exception:
73
- return "", "对话框输出无法解析", True
74
- return str(data.get("path") or ""), "", False
75
-
76
-
77
- def main():
78
- req = {}
79
- try:
80
- req = json.loads(sys.stdin.read() or "{}")
81
- except Exception:
82
- pass
83
- import tkinter as tk
84
- from tkinter import filedialog
85
-
86
- root = tk.Tk()
87
- root.withdraw()
88
- try:
89
- root.attributes("-topmost", True) # 别被全屏浏览器盖住
90
- except Exception:
91
- pass
92
- is_file = req.get("mode") == "file"
93
- kw = {"title": req.get("title") or ("选择文件" if is_file else "选择文件夹")}
94
- init = req.get("initial") or ""
95
- if init and os.path.isdir(init):
96
- kw["initialdir"] = init
97
- try:
98
- if is_file:
99
- kw["filetypes"] = [("备份包", "*.zip"), ("全部文件", "*.*")]
100
- path = filedialog.askopenfilename(**kw) or ""
101
- else:
102
- path = filedialog.askdirectory(**kw) or ""
103
- except Exception:
104
- sys.exit(1)
105
- sys.stdout.write(json.dumps({"path": path}))
106
- root.destroy()
107
-
108
-
109
- if __name__ == "__main__":
110
- main()
1
+ #!/usr/bin/env python3
2
+ """原生「选择文件夹」对话框(main.py 的 /api/pick_folder 用)。
3
+
4
+ Tk 必须活在自家进程的主线程里:HTTP 请求线程里建 root 在 macOS 上会崩,
5
+ Windows 上反复建/销毁也不稳,所以隔离成独立进程。ask_directory() 是父端
6
+ 入口,把本文件拉起为子进程,请求参数(JSON:initial 起始目录 / title 标题)
7
+ 走 stdin,选中目录以 JSON({"path": ...})走 stdout,用户取消回空串;
8
+ 对话框部分是文件尾的 main()。任何一步失败都以非零码退出,父端据此回
9
+ fallback=true 让前端回落网页目录弹框。"""
10
+ import json
11
+ import os
12
+ import subprocess
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ # 非 Windows 置 0:POSIX 的 Popen 对非零 creationflags 抛 ValueError(manager 同款守卫)
17
+ CREATE_NO_WINDOW = 0x08000000 if os.name == "nt" else 0
18
+ _SELF = str(Path(__file__).resolve())
19
+
20
+
21
+ def ask_directory(initial="", title="选择文件夹"):
22
+ """父端入口:拉起子进程弹原生对话框,返回 (path, error, fallback)。
23
+
24
+ 用户取消时 path 为空串;子进程起不来(机器无 tkinter 等)时 error
25
+ 非空且 fallback=True,调用方原样转给前端回落网页目录弹框。"""
26
+ if not Path(__file__).exists():
27
+ return "", "pick_dialog.py 缺失", True
28
+ req = json.dumps({"initial": initial, "title": title}).encode("utf-8")
29
+ env = dict(os.environ, PYTHONIOENCODING="utf-8")
30
+ try:
31
+ if os.name == "nt":
32
+ cp = subprocess.run([sys.executable, _SELF], input=req,
33
+ capture_output=True, timeout=600, env=env,
34
+ creationflags=CREATE_NO_WINDOW)
35
+ else:
36
+ cp = subprocess.run([sys.executable, _SELF], input=req,
37
+ capture_output=True, timeout=600, env=env)
38
+ except Exception as e:
39
+ return "", str(e), True
40
+ if cp.returncode != 0:
41
+ err = (cp.stderr or b"").decode("utf-8", "replace").strip().splitlines()
42
+ return "", (err[-1] if err else "native picker unavailable"), True
43
+ try:
44
+ data = json.loads((cp.stdout or b"").decode("utf-8", "replace") or "{}")
45
+ except Exception:
46
+ return "", "对话框输出无法解析", True
47
+ return str(data.get("path") or ""), "", False
48
+
49
+
50
+ def ask_file(initial="", title="选择文件"):
51
+ """父端入口:拉起子进程弹原生「选择文件」对话框(导入备份包用)。
52
+ 返回 (path, error, fallback),语义与 ask_directory 一致。"""
53
+ if not Path(__file__).exists():
54
+ return "", "pick_dialog.py 缺失", True
55
+ req = json.dumps({"mode": "file", "initial": initial, "title": title}).encode("utf-8")
56
+ env = dict(os.environ, PYTHONIOENCODING="utf-8")
57
+ try:
58
+ if os.name == "nt":
59
+ cp = subprocess.run([sys.executable, _SELF], input=req,
60
+ capture_output=True, timeout=600, env=env,
61
+ creationflags=CREATE_NO_WINDOW)
62
+ else:
63
+ cp = subprocess.run([sys.executable, _SELF], input=req,
64
+ capture_output=True, timeout=600, env=env)
65
+ except Exception as e:
66
+ return "", str(e), True
67
+ if cp.returncode != 0:
68
+ err = (cp.stderr or b"").decode("utf-8", "replace").strip().splitlines()
69
+ return "", (err[-1] if err else "native picker unavailable"), True
70
+ try:
71
+ data = json.loads((cp.stdout or b"").decode("utf-8", "replace") or "{}")
72
+ except Exception:
73
+ return "", "对话框输出无法解析", True
74
+ return str(data.get("path") or ""), "", False
75
+
76
+
77
+ def main():
78
+ req = {}
79
+ try:
80
+ req = json.loads(sys.stdin.read() or "{}")
81
+ except Exception:
82
+ pass
83
+ import tkinter as tk
84
+ from tkinter import filedialog
85
+
86
+ root = tk.Tk()
87
+ root.withdraw()
88
+ try:
89
+ root.attributes("-topmost", True) # 别被全屏浏览器盖住
90
+ except Exception:
91
+ pass
92
+ is_file = req.get("mode") == "file"
93
+ kw = {"title": req.get("title") or ("选择文件" if is_file else "选择文件夹")}
94
+ init = req.get("initial") or ""
95
+ if init and os.path.isdir(init):
96
+ kw["initialdir"] = init
97
+ try:
98
+ if is_file:
99
+ kw["filetypes"] = [("备份包", "*.zip"), ("全部文件", "*.*")]
100
+ path = filedialog.askopenfilename(**kw) or ""
101
+ else:
102
+ path = filedialog.askdirectory(**kw) or ""
103
+ except Exception:
104
+ sys.exit(1)
105
+ sys.stdout.write(json.dumps({"path": path}))
106
+ root.destroy()
107
+
108
+
109
+ if __name__ == "__main__":
110
+ main()
package/app/ui/app.js CHANGED
@@ -451,7 +451,7 @@ function renderTypeOptions() {
451
451
  if (prev && flowById(prev) && (S.typeTouched || prev !== "direct")) sel.value = prev;
452
452
  // 偏好记忆:上次创建用的类型优先于出厂默认「直接执行」(老数据/已删流程回落)
453
453
  else if (S.lastPrefs && S.lastPrefs.type && flowById(S.lastPrefs.type)) sel.value = S.lastPrefs.type;
454
- // 首屏默认落在「直接执行」(快档位:单 CLI 直达,无拆解/评审);
454
+ // 首屏默认落在「直接执行」(快档位:直连模型 API,无拆解/评审);
455
455
  // 老数据或该流程被删时保持首项,不硬造一个不存在的值
456
456
  else if (flowById("direct")) sel.value = "direct";
457
457
  applyTaskPreferenceDefaults();
@@ -14699,8 +14699,7 @@ async function suStartupCheck() {
14699
14699
  }
14700
14700
 
14701
14701
  /* ---------------------------------------------------------- 页签 & 初始化 */
14702
- const TAB_TITLES = { overview: "概览", tasks: "任务", runs: "运行记录", automation: "自动化", browser: "浏览器", zentao: "禅道 Bug 自动修复", __wxdigest: "群摘要", usage: "用量统计", agents: "本机智能体", models: "模型接入", bindings: "模型调度(可选)", skills: "经验库", knowledge: "知识库", market: "插件市场", orch: "编排设置", data: "数据与备份", appearance: "皮肤", about: "关于与更新" };
14703
- const SET_TABS = new Set(Object.keys(TAB_TITLES)); // 全部设置子页(__phone 是弹框,不算)
14702
+ const TAB_TITLES = { overview: "概览", tasks: "任务", runs: "运行记录", automation: "自动化", browser: "浏览器", zentao: "禅道 Bug 自动修复", __wxdigest: "群摘要", usage: "用量统计", agents: "本机智能体", models: "模型接入", bindings: "模型调度(可选)", skills: "经验库", knowledge: "知识库", market: "插件市场", orch: "编排设置", evalbench: "模型评测", data: "数据与备份", appearance: "皮肤", about: "关于与更新" };
14704
14703
 
14705
14704
  function tabTitle(name) {
14706
14705
  return t(TAB_TITLES[name]) || t("设置");
package/app/ui/artdiff.js CHANGED
@@ -1,74 +1,74 @@
1
- /* 成品两版对比的纯 diff 计算(OpenCreator「版本化对比」借鉴)。
2
- * 独立文件无 DOM 依赖:app.js 用 window.ArtifactDiff 渲染,tests 用
3
- * module.exports 直测。算法:公共前后缀修剪 + 中段 LCS(上限保护,
4
- * 超限诚实降级为整块替换计数,不装作对齐了)。 */
5
- (function (root, factory) {
6
- var api = factory();
7
- if (typeof window !== "undefined") window.ArtifactDiff = api;
8
- if (typeof module !== "undefined" && module.exports) module.exports = api;
9
- })(typeof self !== "undefined" ? self : this, function () {
10
- "use strict";
11
-
12
- var MID_CAP = 400; // 中段行数单边上限
13
- var MID_PRODUCT_CAP = 160000; // LCS DP 格子上限(400×400)
14
-
15
- /* LCS 回溯产出统一 diff 行:[{t:"="|"-"/"+", s:行文本}](只对中段)。 */
16
- function lcsRows(A, B) {
17
- var n = A.length, m = B.length;
18
- var dp = [];
19
- for (var k = 0; k <= n; k++) dp.push(new Uint32Array(m + 1));
20
- for (var i = n - 1; i >= 0; i--)
21
- for (var j = m - 1; j >= 0; j--)
22
- dp[i][j] = A[i] === B[j] ? dp[i + 1][j + 1] + 1
23
- : (dp[i + 1][j] >= dp[i][j + 1] ? dp[i + 1][j] : dp[i][j + 1]);
24
- var rows = [];
25
- i = 0; j = 0;
26
- while (i < n && j < m) {
27
- if (A[i] === B[j]) { rows.push({ t: "=", s: A[i] }); i++; j++; }
28
- else if (dp[i + 1][j] >= dp[i][j + 1]) { rows.push({ t: "-", s: A[i] }); i++; }
29
- else { rows.push({ t: "+", s: B[j] }); j++; }
30
- }
31
- while (i < n) { rows.push({ t: "-", s: A[i] }); i++; }
32
- while (j < m) { rows.push({ t: "+", s: B[j] }); j++; }
33
- return rows;
34
- }
35
-
36
- /* 对外主口:旧版 a、新版 b → {
37
- * prefix/suffix: 相同行数(渲染端折叠展示),
38
- * identical: 布尔,两版一致,
39
- * rows: 中段统一 diff 行(超限时为 null),
40
- * oversized: {a, b} 超限时中段行数(诚实整块计数) } */
41
- function diff(a, b) {
42
- a = String(a == null ? "" : a);
43
- b = String(b == null ? "" : b);
44
- var A = a.split("\n"), B = b.split("\n");
45
- var p = 0;
46
- while (p < A.length && p < B.length && A[p] === B[p]) p++;
47
- var s = 0;
48
- while (s < A.length - p && s < B.length - p &&
49
- A[A.length - 1 - s] === B[B.length - 1 - s]) s++;
50
- var midA = A.slice(p, A.length - s), midB = B.slice(p, B.length - s);
51
- var out = { prefix: p, suffix: s, identical: !midA.length && !midB.length,
52
- rows: null, oversized: null };
53
- if (out.identical) return out;
54
- if (midA.length > MID_CAP || midB.length > MID_CAP ||
55
- midA.length * midB.length > MID_PRODUCT_CAP) {
56
- out.oversized = { a: midA.length, b: midB.length };
57
- return out;
58
- }
59
- out.rows = lcsRows(midA, midB);
60
- return out;
61
- }
62
-
63
- /* rows → 摘要计数(+x/-y,渲染端徽章用)。 */
64
- function counts(d) {
65
- var add = 0, del = 0;
66
- if (d.oversized) return { add: d.oversized.b, del: d.oversized.a };
67
- (d.rows || []).forEach(function (r) {
68
- if (r.t === "+") add++; else if (r.t === "-") del++;
69
- });
70
- return { add: add, del: del };
71
- }
72
-
73
- return { diff: diff, counts: counts, lcsRows: lcsRows };
74
- });
1
+ /* 成品两版对比的纯 diff 计算(OpenCreator「版本化对比」借鉴)。
2
+ * 独立文件无 DOM 依赖:app.js 用 window.ArtifactDiff 渲染,tests 用
3
+ * module.exports 直测。算法:公共前后缀修剪 + 中段 LCS(上限保护,
4
+ * 超限诚实降级为整块替换计数,不装作对齐了)。 */
5
+ (function (root, factory) {
6
+ var api = factory();
7
+ if (typeof window !== "undefined") window.ArtifactDiff = api;
8
+ if (typeof module !== "undefined" && module.exports) module.exports = api;
9
+ })(typeof self !== "undefined" ? self : this, function () {
10
+ "use strict";
11
+
12
+ var MID_CAP = 400; // 中段行数单边上限
13
+ var MID_PRODUCT_CAP = 160000; // LCS DP 格子上限(400×400)
14
+
15
+ /* LCS 回溯产出统一 diff 行:[{t:"="|"-"/"+", s:行文本}](只对中段)。 */
16
+ function lcsRows(A, B) {
17
+ var n = A.length, m = B.length;
18
+ var dp = [];
19
+ for (var k = 0; k <= n; k++) dp.push(new Uint32Array(m + 1));
20
+ for (var i = n - 1; i >= 0; i--)
21
+ for (var j = m - 1; j >= 0; j--)
22
+ dp[i][j] = A[i] === B[j] ? dp[i + 1][j + 1] + 1
23
+ : (dp[i + 1][j] >= dp[i][j + 1] ? dp[i + 1][j] : dp[i][j + 1]);
24
+ var rows = [];
25
+ i = 0; j = 0;
26
+ while (i < n && j < m) {
27
+ if (A[i] === B[j]) { rows.push({ t: "=", s: A[i] }); i++; j++; }
28
+ else if (dp[i + 1][j] >= dp[i][j + 1]) { rows.push({ t: "-", s: A[i] }); i++; }
29
+ else { rows.push({ t: "+", s: B[j] }); j++; }
30
+ }
31
+ while (i < n) { rows.push({ t: "-", s: A[i] }); i++; }
32
+ while (j < m) { rows.push({ t: "+", s: B[j] }); j++; }
33
+ return rows;
34
+ }
35
+
36
+ /* 对外主口:旧版 a、新版 b → {
37
+ * prefix/suffix: 相同行数(渲染端折叠展示),
38
+ * identical: 布尔,两版一致,
39
+ * rows: 中段统一 diff 行(超限时为 null),
40
+ * oversized: {a, b} 超限时中段行数(诚实整块计数) } */
41
+ function diff(a, b) {
42
+ a = String(a == null ? "" : a);
43
+ b = String(b == null ? "" : b);
44
+ var A = a.split("\n"), B = b.split("\n");
45
+ var p = 0;
46
+ while (p < A.length && p < B.length && A[p] === B[p]) p++;
47
+ var s = 0;
48
+ while (s < A.length - p && s < B.length - p &&
49
+ A[A.length - 1 - s] === B[B.length - 1 - s]) s++;
50
+ var midA = A.slice(p, A.length - s), midB = B.slice(p, B.length - s);
51
+ var out = { prefix: p, suffix: s, identical: !midA.length && !midB.length,
52
+ rows: null, oversized: null };
53
+ if (out.identical) return out;
54
+ if (midA.length > MID_CAP || midB.length > MID_CAP ||
55
+ midA.length * midB.length > MID_PRODUCT_CAP) {
56
+ out.oversized = { a: midA.length, b: midB.length };
57
+ return out;
58
+ }
59
+ out.rows = lcsRows(midA, midB);
60
+ return out;
61
+ }
62
+
63
+ /* rows → 摘要计数(+x/-y,渲染端徽章用)。 */
64
+ function counts(d) {
65
+ var add = 0, del = 0;
66
+ if (d.oversized) return { add: d.oversized.b, del: d.oversized.a };
67
+ (d.rows || []).forEach(function (r) {
68
+ if (r.t === "+") add++; else if (r.t === "-") del++;
69
+ });
70
+ return { add: add, del: del };
71
+ }
72
+
73
+ return { diff: diff, counts: counts, lcsRows: lcsRows };
74
+ });
package/app/ui/i18n.js CHANGED
@@ -1409,7 +1409,6 @@
1409
1409
  "直连引擎(单智能体直达,无拆解/评审,快)": "Direct engine (single-agent straight through — no decomposition/review, fast)",
1410
1410
  "直接执行": "Direct run",
1411
1411
  "让 AI 直接做什么(一句话,可带附件)": "What should the AI just do? (one sentence, attachments welcome)",
1412
- "单智能体直达:目标+附件交给一个 CLI 跑完即止,无拆解/评审(快)": "Single-agent straight through: goal + attachments go to one CLI and that's it — no decomposition or review (fast)",
1413
1412
  "发现新版本,点击前往更新": "New version found — click to update",
1414
1413
  "已忽略该版本提醒,可随时在「关于与更新」里升级": "This version's reminder is dismissed — upgrade anytime in \"About & Updates\"",
1415
1414
  "已是最新版": "Already the latest version",