codebee 0.1.13 → 0.1.15
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.
- package/CHANGELOG.md +15 -0
- package/README.md +42 -8
- package/app/core/automation.py +7 -0
- package/app/core/catalog.py +9 -1
- package/app/core/env_scrub.py +5 -3
- package/app/core/flows.py +3 -0
- package/app/core/goal_service.py +11 -2
- package/app/core/jobs.py +38 -11
- package/app/core/market_remote.py +6 -3
- package/app/core/modelhub.py +45 -18
- package/app/core/notify.py +96 -0
- package/app/core/paihang.py +106 -0
- package/app/core/pipeline.py +26 -15
- package/app/core/publish/browser.py +63 -3
- package/app/core/publish/fanqie.py +16 -0
- package/app/core/publish/flow.py +25 -1
- package/app/core/publish/flows-fanqie-calibrated.json +37 -0
- package/app/core/publish/flows-qimao-calibrated.json +233 -0
- package/app/core/publish/manager.py +73 -13
- package/app/core/publish/qimao.py +13 -0
- package/app/core/settings.py +3 -1
- package/app/core/settings_schema.py +15 -2
- package/app/core/share_page.py +111 -0
- package/app/core/step_runner.py +23 -1
- package/app/core/telemetry.py +3 -2
- package/app/core/tlsctx.py +64 -0
- package/app/core/zentao.py +1302 -0
- package/app/main.py +89 -4
- package/app/ui/app.js +212 -1
- package/app/ui/i18n.js +54 -0
- package/app/ui/index.html +50 -0
- package/app/ui/style.css +27 -0
- package/package.json +1 -1
|
@@ -149,6 +149,12 @@ def revision(ns):
|
|
|
149
149
|
return _REVISIONS.get(ns, 0)
|
|
150
150
|
|
|
151
151
|
|
|
152
|
+
def names():
|
|
153
|
+
"""已注册 namespace 清单(GET /api/settings-v2 用)。"""
|
|
154
|
+
with _LOCK:
|
|
155
|
+
return sorted(_NAMESPACES)
|
|
156
|
+
|
|
157
|
+
|
|
152
158
|
def mutate(ns, ops, expected_revision=None):
|
|
153
159
|
"""写一组操作。ops = [{"op":"set","path":..., "value":...}, ...]。
|
|
154
160
|
|
|
@@ -189,15 +195,22 @@ def mutate(ns, ops, expected_revision=None):
|
|
|
189
195
|
|
|
190
196
|
|
|
191
197
|
def describe(ns, redact_secrets=True):
|
|
192
|
-
"""给 UI 的视图:默认把 redact 字段替换为 __REDACTED__(不泄露真值)。
|
|
198
|
+
"""给 UI 的视图:默认把 redact 字段替换为 __REDACTED__(不泄露真值)。
|
|
199
|
+
|
|
200
|
+
fields 带回字段元数据(类型/说明/choices/clamp),前端调参卡按 schema
|
|
201
|
+
渲染控件,schema 变更零前端改动。"""
|
|
193
202
|
with _LOCK:
|
|
194
203
|
vals = json.loads(json.dumps(_VALUES.get(ns, {}))) # deep copy
|
|
195
204
|
ndef = _NAMESPACES.get(ns) or {"fields": {}}
|
|
205
|
+
fields = [{"path": f.path, "type": f.ftype, "description": f.description,
|
|
206
|
+
"choices": f.choices, "clamp": f.clamp}
|
|
207
|
+
for f in ndef["fields"].values()]
|
|
196
208
|
for f in ndef["fields"].values():
|
|
197
209
|
if f.redact and redact_secrets:
|
|
198
210
|
if _get_path(vals, f.path) not in (None, ""):
|
|
199
211
|
_set_path(vals, f.path, "__REDACTED__")
|
|
200
|
-
return {"ns": ns, "revision": _REVISIONS.get(ns, 0), "values": vals
|
|
212
|
+
return {"ns": ns, "revision": _REVISIONS.get(ns, 0), "values": vals,
|
|
213
|
+
"fields": fields}
|
|
201
214
|
|
|
202
215
|
|
|
203
216
|
# ---- 默认 namespace(编排阈值的 schema 化存放处;pipeline 可渐进接入) ----
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""可分享报告页(借鉴 agency-orchestrator 的 ao report):把 run 成果渲染成
|
|
3
|
+
自包含单文件 HTML——零 JS、内联样式、离线可看,直接发群/发人。
|
|
4
|
+
|
|
5
|
+
纯函数无网络;数据全部来自调用方(store/paths 已读好的对象)。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import html as _html
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _esc(s):
|
|
14
|
+
return _html.escape(str(s if s is not None else ""), quote=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _md_ish(text):
|
|
18
|
+
"""极简 Markdown 呈现:标题/列表/粗体/代码,够报告阅读即可,不做完整解析。"""
|
|
19
|
+
out = []
|
|
20
|
+
for ln in (text or "").splitlines():
|
|
21
|
+
e = _esc(ln)
|
|
22
|
+
if ln.startswith("# "):
|
|
23
|
+
out.append("<h1>%s</h1>" % e[2:])
|
|
24
|
+
elif ln.startswith("## "):
|
|
25
|
+
out.append("<h2>%s</h2>" % e[3:])
|
|
26
|
+
elif ln.startswith("### "):
|
|
27
|
+
out.append("<h3>%s</h3>" % e[4:])
|
|
28
|
+
elif ln.startswith("- "):
|
|
29
|
+
out.append("<li>%s</li>" % e[2:])
|
|
30
|
+
else:
|
|
31
|
+
out.append("<p>%s</p>" % e)
|
|
32
|
+
return "\n".join(out)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
_SHARE_TPL = """<!DOCTYPE html>
|
|
36
|
+
<html lang="zh"><head><meta charset="utf-8">
|
|
37
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
38
|
+
<title>__TITLE__ · CodeBee 任务报告</title>
|
|
39
|
+
<style>
|
|
40
|
+
body{margin:0;background:#f5f6f8;color:#1c2330;font:15px/1.65 -apple-system,"PingFang SC","Microsoft YaHei",sans-serif}
|
|
41
|
+
.wrap{max-width:760px;margin:0 auto;padding:28px 18px 60px}
|
|
42
|
+
.head{border-bottom:3px solid #2b6cb0;padding-bottom:14px;margin-bottom:22px}
|
|
43
|
+
h1{margin:0 0 6px;font-size:24px}
|
|
44
|
+
.meta{color:#5a6472;font-size:13px}
|
|
45
|
+
.badge{display:inline-block;border-radius:999px;padding:2px 12px;font-size:13px;font-weight:600}
|
|
46
|
+
.badge.ok{background:#e6f6ec;color:#177245}.badge.bad{background:#fdeaea;color:#b42318}
|
|
47
|
+
.badge.off{background:#eef0f3;color:#5a6472}
|
|
48
|
+
.card{background:#fff;border:1px solid #e3e6ea;border-radius:12px;padding:18px 20px;margin-bottom:18px}
|
|
49
|
+
.card h2{font-size:16px;margin:0 0 12px;color:#2b6cb0}
|
|
50
|
+
table{border-collapse:collapse;width:100%}
|
|
51
|
+
td{padding:6px 10px;border-bottom:1px solid #eef0f3}
|
|
52
|
+
tr.sum td{font-weight:700;border-top:2px solid #e3e6ea}
|
|
53
|
+
.step{display:flex;gap:10px;padding:7px 0;border-bottom:1px dashed #eef0f3;font-size:13.5px;align-items:baseline}
|
|
54
|
+
.step .n{color:#8a93a0;font-weight:700}.step .role{font-weight:600;min-width:88px}
|
|
55
|
+
.step .who{color:#5a6472;min-width:90px}.step .sum{flex:1}.st{font-size:12px}
|
|
56
|
+
.st.done{color:#177245}.st.failed{color:#b42318}
|
|
57
|
+
h1,h2,h3{margin:14px 0 8px}li{margin:4px 0}.trow{padding:2px 0;border-bottom:1px dotted #eee}
|
|
58
|
+
p{margin:8px 0}.mut{color:#8a93a0}code{background:#f0f2f5;padding:1px 6px;border-radius:4px}
|
|
59
|
+
pre{background:#f0f2f5;padding:12px;border-radius:8px;overflow-x:auto;white-space:pre-wrap}
|
|
60
|
+
</style></head><body><div class="wrap">
|
|
61
|
+
<div class="head"><h1>__TITLE__</h1>
|
|
62
|
+
<div class="meta">__META__ · __ST__ · CodeBee 生成于 __NOW__</div></div>
|
|
63
|
+
<div class="card"><h2>结果</h2><span class="badge __STCLS__">__ST__</span>__OVERALL__</div>
|
|
64
|
+
<div class="card"><h2>步骤</h2>__STEPS__</div>
|
|
65
|
+
<div class="card"><h2>报告</h2>__REPORT__</div>
|
|
66
|
+
<div class="meta">由 <b>CodeBee</b> 多智能体编排台生成</div>
|
|
67
|
+
</div></body></html>"""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def render_share_html(run, task, report_text):
|
|
71
|
+
"""渲染自包含分享页。run/task/report_text 缺失时优雅降级。"""
|
|
72
|
+
run = run or {}
|
|
73
|
+
task = task or {}
|
|
74
|
+
status = str(run.get("status") or "")
|
|
75
|
+
st_label = {"done": "已完成", "failed": "失败", "cancelled": "已取消",
|
|
76
|
+
"running": "进行中"}.get(status, status)
|
|
77
|
+
st_cls = {"done": "ok", "failed": "bad", "cancelled": "off"}.get(status, "off")
|
|
78
|
+
verdict = run.get("verdict") or {}
|
|
79
|
+
means = verdict.get("scores") or {}
|
|
80
|
+
|
|
81
|
+
score_rows = ""
|
|
82
|
+
for d, v in means.items():
|
|
83
|
+
score_rows += "<tr><td>%s</td><td>%.1f</td></tr>" % (_esc(d), float(v))
|
|
84
|
+
if verdict:
|
|
85
|
+
score_rows += ('<tr class="sum"><td>综合</td>'
|
|
86
|
+
'<td>%.1f</td></tr>' % float(verdict.get("overall") or 0))
|
|
87
|
+
|
|
88
|
+
steps_rows = ""
|
|
89
|
+
for s in run.get("steps") or []:
|
|
90
|
+
steps_rows += (
|
|
91
|
+
'<div class="step"><span class="n">%02d</span>'
|
|
92
|
+
'<span class="role">%s</span><span class="who">%s</span>'
|
|
93
|
+
'<span class="sum">%s</span><span class="st %s">%s</span></div>'
|
|
94
|
+
% (s.get("n") or 0, _esc(s.get("role") or ""),
|
|
95
|
+
_esc(s.get("agent_label") or s.get("agent") or ""),
|
|
96
|
+
_esc((s.get("summary") or "")[:220]), _esc(s.get("status") or ""),
|
|
97
|
+
_esc(s.get("status") or "")))
|
|
98
|
+
|
|
99
|
+
report_html = _md_ish(report_text) if report_text else "<p class='mut'>(暂无报告)</p>"
|
|
100
|
+
overall_note = ("综合 %.1f 分" % float(verdict.get("overall"))
|
|
101
|
+
if verdict.get("overall") is not None else "")
|
|
102
|
+
|
|
103
|
+
return (_SHARE_TPL
|
|
104
|
+
.replace("__TITLE__", _esc(task.get("title") or run.get("title") or "任务报告"))
|
|
105
|
+
.replace("__META__", _esc(task.get("goal") or ""))
|
|
106
|
+
.replace("__ST__", _esc(st_label))
|
|
107
|
+
.replace("__STCLS__", st_cls)
|
|
108
|
+
.replace("__NOW__", _esc(time.strftime("%Y-%m-%d %H:%M")))
|
|
109
|
+
.replace("__OVERALL__", _esc(overall_note))
|
|
110
|
+
.replace("__STEPS__", steps_rows or "<p class='mut'>(无步骤)</p>")
|
|
111
|
+
.replace("__REPORT__", report_html))
|
package/app/core/step_runner.py
CHANGED
|
@@ -14,7 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
|
|
15
15
|
import logging
|
|
16
16
|
|
|
17
|
-
from .compaction import maybe_compact
|
|
17
|
+
from .compaction import maybe_compact, DEFAULT_PRESSURE_THRESHOLD
|
|
18
18
|
from .error_codes import ErrorCode
|
|
19
19
|
|
|
20
20
|
log = logging.getLogger(__name__)
|
|
@@ -28,6 +28,23 @@ _OVERFLOW_CODES = {ErrorCode.CONTEXT_OVERFLOW, ErrorCode.MAX_TOKENS}
|
|
|
28
28
|
_PRECHECK_RATIO = 0.9
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
def _v2_compaction_tuning():
|
|
32
|
+
"""settings_v2 orchestrator.compaction 微调 → (threshold|None, retain|None)。
|
|
33
|
+
|
|
34
|
+
None = 未配置(走 maybe_compact 的模块默认);读取/注册失败静默回落。
|
|
35
|
+
retain 允许 0(尾部不保留),与 None(未配置)语义不同,勿合并。"""
|
|
36
|
+
try:
|
|
37
|
+
from .settings_schema import get as ss_get, register_default_namespaces
|
|
38
|
+
register_default_namespaces()
|
|
39
|
+
c = (ss_get("orchestrator") or {}).get("compaction") or {}
|
|
40
|
+
th = c.get("pressure_threshold")
|
|
41
|
+
rt = c.get("retain_tail_tokens")
|
|
42
|
+
return (float(th) if th else None,
|
|
43
|
+
int(rt) if rt is not None else None)
|
|
44
|
+
except Exception:
|
|
45
|
+
return None, None
|
|
46
|
+
|
|
47
|
+
|
|
31
48
|
def execute_step(session, run_agent_fn, prompt, *, model: str = "",
|
|
32
49
|
llm_caller=None, retain_tail_tokens=None, **kwargs):
|
|
33
50
|
"""执行一次 step;撑爆时压缩并守门重试一次。
|
|
@@ -47,9 +64,14 @@ def execute_step(session, run_agent_fn, prompt, *, model: str = "",
|
|
|
47
64
|
Returns:
|
|
48
65
|
(result, retried: bool)
|
|
49
66
|
"""
|
|
67
|
+
v2_th, v2_rt = _v2_compaction_tuning()
|
|
50
68
|
compact_kwargs = {}
|
|
51
69
|
if retain_tail_tokens is not None:
|
|
52
70
|
compact_kwargs["retain_tail_tokens"] = retain_tail_tokens
|
|
71
|
+
elif v2_rt is not None:
|
|
72
|
+
compact_kwargs["retain_tail_tokens"] = v2_rt
|
|
73
|
+
if v2_th is not None:
|
|
74
|
+
compact_kwargs["threshold"] = v2_th
|
|
53
75
|
if llm_caller is not None and model:
|
|
54
76
|
try:
|
|
55
77
|
from .token_meter import token_meter
|
package/app/core/telemetry.py
CHANGED
|
@@ -29,7 +29,7 @@ import time
|
|
|
29
29
|
import urllib.request
|
|
30
30
|
import zipfile
|
|
31
31
|
|
|
32
|
-
from . import errorlog, paths, settings
|
|
32
|
+
from . import errorlog, paths, settings, tlsctx
|
|
33
33
|
|
|
34
34
|
# 端点:部署后把 CloudBase HTTP 函数 URL 填到这里(见 cloudfunctions/telemetry-collect/)
|
|
35
35
|
ENDPOINT = os.environ.get("TUTTI_TELEMETRY_URL", "").strip()
|
|
@@ -107,7 +107,8 @@ def _post(url, payload):
|
|
|
107
107
|
headers={"Content-Type": "application/json",
|
|
108
108
|
"User-Agent": "CodeBee-Telemetry/1"},
|
|
109
109
|
method="POST")
|
|
110
|
-
with urllib.request.urlopen(req, timeout=TIMEOUT_S
|
|
110
|
+
with urllib.request.urlopen(req, timeout=TIMEOUT_S,
|
|
111
|
+
context=tlsctx.context()) as resp:
|
|
111
112
|
return int(resp.status or 0)
|
|
112
113
|
except Exception:
|
|
113
114
|
return 0
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""HTTPS 证书校验上下文:补齐 macOS 上 Python 缺失的 CA 源(不关校验)。
|
|
3
|
+
|
|
4
|
+
现象:用官方 pkg 装 Node 的 Mac 上往往再装 python.org 的 Python,它不读
|
|
5
|
+
系统钥匙串,默认验证路径下没有根证书,所有 HTTPS 请求报
|
|
6
|
+
CERTIFICATE_VERIFY_FAILED(unable to get local issuer certificate)。
|
|
7
|
+
|
|
8
|
+
修法是给默认上下文**追加**可用 CA 源,校验语义只增不减:
|
|
9
|
+
1. certifi(装了就用,跨平台最全);
|
|
10
|
+
2. /etc/ssl/cert.pem(macOS 系统自带 CA 束,Catalina 起就有)。
|
|
11
|
+
两个都拿不到时返回默认上下文——报错与旧行为一致,绝不静默关校验。
|
|
12
|
+
"""
|
|
13
|
+
import ssl
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def cafiles():
|
|
18
|
+
"""候选 CA 束路径;坏路径/不存在由 load_verify_locations 抛错后被吞。"""
|
|
19
|
+
out = []
|
|
20
|
+
try:
|
|
21
|
+
import certifi
|
|
22
|
+
out.append(certifi.where())
|
|
23
|
+
except Exception:
|
|
24
|
+
pass
|
|
25
|
+
if sys.platform == "darwin":
|
|
26
|
+
out.append("/etc/ssl/cert.pem")
|
|
27
|
+
return out
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _build():
|
|
31
|
+
ctx = ssl.create_default_context()
|
|
32
|
+
for f in cafiles():
|
|
33
|
+
try:
|
|
34
|
+
ctx.load_verify_locations(cafile=f)
|
|
35
|
+
except Exception:
|
|
36
|
+
pass
|
|
37
|
+
return ctx
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
_CTX = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def context():
|
|
44
|
+
"""带兜底 CA 源的 ssl.SSLContext(进程内缓存;校验语义与默认一致)。"""
|
|
45
|
+
global _CTX
|
|
46
|
+
if _CTX is None:
|
|
47
|
+
_CTX = _build()
|
|
48
|
+
return _CTX
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def humanize(err_text):
|
|
52
|
+
"""证书验证失败 → 可行动的人话提示(非证书类错误原样返回)。
|
|
53
|
+
|
|
54
|
+
兜底 CA 仍验证失败只剩两类真实原因:本机代理对 HTTPS 做 TLS 拦截
|
|
55
|
+
(其根证书只在系统钥匙串/浏览器里,Python 的静态 CA 束不认),或极端
|
|
56
|
+
环境连 /etc/ssl/cert.pem 都没有。SSL_CERT_FILE 是 OpenSSL 标准逃生门,
|
|
57
|
+
create_default_context() 本就吃它,无需额外代码。"""
|
|
58
|
+
text = str(err_text or "")
|
|
59
|
+
if "CERTIFICATE_VERIFY_FAILED" not in text:
|
|
60
|
+
return text
|
|
61
|
+
return (text + " —— 证书验证仍失败:① 若开着代理/安全软件且开启了 HTTPS 拦截,"
|
|
62
|
+
"关掉拦截,或把其根证书导出后设环境变量 SSL_CERT_FILE 指向它再启动;"
|
|
63
|
+
"② 确认系统存在 /etc/ssl/cert.pem;③ 刚升级过 CodeBee 的话,"
|
|
64
|
+
"旧服务进程可能还在跑——关掉终端窗口重开")
|