codebee 0.1.71 → 0.1.72
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 +595 -589
- package/LICENSE +21 -21
- package/README.md +508 -509
- package/app/core/acceptance.py +100 -100
- package/app/core/aiflavor.py +197 -197
- package/app/core/artifacts.py +148 -148
- package/app/core/assets.py +110 -110
- package/app/core/attachments.py +623 -623
- package/app/core/audit.py +30 -30
- package/app/core/backup.py +470 -470
- package/app/core/beekeeper.py +183 -183
- package/app/core/benchstore.py +95 -95
- package/app/core/board.py +209 -209
- package/app/core/bookmeta.py +935 -935
- package/app/core/bookmeta_catalog.py +180 -180
- package/app/core/branching.py +137 -137
- package/app/core/builtin_agent.py +1680 -1673
- package/app/core/capability.py +144 -144
- package/app/core/chaptersafe.py +32 -32
- package/app/core/cleanup.py +303 -303
- package/app/core/compaction.py +201 -201
- package/app/core/covergen.py +356 -356
- package/app/core/defectretro.py +43 -43
- package/app/core/diagnostics.py +114 -114
- package/app/core/dispatch.py +190 -190
- package/app/core/dispatch_log.py +128 -128
- package/app/core/env_scrub.py +85 -85
- package/app/core/errorlog.py +137 -137
- package/app/core/evalbench.py +836 -720
- package/app/core/evaluation.py +174 -174
- package/app/core/failure_notify.py +290 -290
- package/app/core/flows.py +7 -7
- package/app/core/goal_service.py +167 -167
- package/app/core/health.py +374 -374
- package/app/core/history.py +38 -38
- package/app/core/hooks.py +162 -162
- package/app/core/jobs.py +21 -0
- package/app/core/manager.py +2143 -2143
- package/app/core/market.py +88 -2
- package/app/core/market_remote.py +16 -0
- package/app/core/mocks.py +64 -64
- package/app/core/modelhub.py +3 -2
- package/app/core/notify.py +201 -201
- package/app/core/novel_quality.py +189 -189
- package/app/core/operations.py +228 -228
- package/app/core/paihang.py +138 -138
- package/app/core/pipeline.py +102 -30
- package/app/core/portguard.py +111 -111
- package/app/core/preview.py +293 -293
- package/app/core/prompt_guard.py +78 -0
- package/app/core/publish/__init__.py +7 -7
- package/app/core/publish/auto.py +396 -367
- package/app/core/publish/browser.py +655 -655
- package/app/core/publish/fanqie.py +165 -165
- package/app/core/publish/flow.py +589 -589
- package/app/core/publish/flows-fanqie-calibrated.json +336 -336
- package/app/core/publish/ledger.py +307 -229
- package/app/core/publish/manager.py +931 -824
- package/app/core/publish/qimao.py +146 -146
- package/app/core/publish/ws.py +139 -139
- package/app/core/quality_gate.py +194 -0
- package/app/core/redact.py +38 -38
- package/app/core/repeat_guard.py +129 -129
- package/app/core/router.py +398 -398
- package/app/core/runner.py +2142 -2142
- package/app/core/selfupdate.py +384 -384
- package/app/core/session_log.py +161 -161
- package/app/core/sessions.py +312 -312
- package/app/core/settings_schema.py +270 -270
- package/app/core/share_page.py +114 -114
- package/app/core/skill_scan.py +139 -122
- package/app/core/skillpacks/fanqie-novel.md +80 -80
- package/app/core/skillpacks/market/character-bible.md +66 -66
- package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
- package/app/core/skillpacks/market/git-workflow.md +57 -57
- package/app/core/skillpacks/market/release-notes.md +72 -72
- package/app/core/skillpacks/market/weekly-report.md +71 -71
- package/app/core/skillpacks/market/worldview-consistency.md +70 -70
- package/app/core/skills.py +58 -8
- package/app/core/step_runner.py +132 -132
- package/app/core/store.py +51 -0
- package/app/core/task_compile.py +199 -199
- package/app/core/telemetry.py +292 -292
- package/app/core/tlsctx.py +64 -64
- package/app/core/token_meter.py +147 -147
- package/app/core/tracing.py +144 -144
- package/app/core/upstream.py +41 -41
- package/app/core/usage.py +1035 -1035
- package/app/core/volumes.py +389 -389
- package/app/core/wxdigest.py +858 -858
- package/app/core/zentao.py +2692 -2614
- package/app/main.py +86 -11
- package/app/mcp_server.py +175 -175
- package/app/pick_dialog.py +110 -110
- package/app/ui/app.js +258 -54
- package/app/ui/artdiff.js +74 -74
- package/app/ui/board.html +162 -162
- package/app/ui/board.js +305 -305
- package/app/ui/i18n.js +24 -0
- package/app/ui/icons/bee.svg +79 -79
- package/app/ui/index.html +5 -0
- package/app/ui/manifest.json +16 -16
- package/app/ui/qrcode.js +2297 -2297
- package/app/ui/style.css +7051 -7012
- package/bin/tutti.js +147 -147
- package/package.json +48 -47
- package/tools/wx_reader.py +261 -261
- package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +0 -88
- package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +0 -126
- package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +0 -210
- package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
- package/app/.playwright-cli/run-detail-after.png +0 -0
- package/app//344/275/234/345/223/201/344/277/241/346/201/257-/344/270/203/347/214/253.md +0 -18
package/app/core/market.py
CHANGED
|
@@ -120,6 +120,31 @@ def _is_market_file(path, pack_id):
|
|
|
120
120
|
return "source: market" in head and ("market_id: %s" % pack_id) in head
|
|
121
121
|
|
|
122
122
|
|
|
123
|
+
def _pack_digest(files):
|
|
124
|
+
"""包内容指纹(sha256 前 16):升级突变对账用——内容一致则同值。"""
|
|
125
|
+
import hashlib
|
|
126
|
+
blob = json.dumps({k: files[k] for k in sorted(files)}, ensure_ascii=False)
|
|
127
|
+
return hashlib.sha256(blob.encode("utf-8")).hexdigest()[:16]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _similar_names(name, installed, cutoff=0.82):
|
|
131
|
+
"""typosquatting 特征近似名对账(difflib 序列相似度):与已装技能名过近的
|
|
132
|
+
返回 [(已装名, 相似度)]。降本求准——cutoff 以上才算「过近」。"""
|
|
133
|
+
import difflib
|
|
134
|
+
low = str(name or "").strip().lower()
|
|
135
|
+
if not low:
|
|
136
|
+
return []
|
|
137
|
+
out = []
|
|
138
|
+
for pid, rec in (installed or {}).items():
|
|
139
|
+
prev = str((rec or {}).get("name") or "").strip().lower()
|
|
140
|
+
if not prev or prev == low:
|
|
141
|
+
continue
|
|
142
|
+
ratio = difflib.SequenceMatcher(None, low, prev).ratio()
|
|
143
|
+
if ratio >= cutoff:
|
|
144
|
+
out.append(((rec or {}).get("name") or pid, round(ratio, 2)))
|
|
145
|
+
return sorted(out, key=lambda x: -x[1])[:3]
|
|
146
|
+
|
|
147
|
+
|
|
123
148
|
# ---------------------------------------------------------------- 安装状态(持久化)
|
|
124
149
|
|
|
125
150
|
def _load_registry():
|
|
@@ -260,6 +285,14 @@ def install_files(pack_id, name, files, extra=None):
|
|
|
260
285
|
udir = _user_pack_dir()
|
|
261
286
|
reg = _load_registry()
|
|
262
287
|
installed = reg.setdefault("installed", {})
|
|
288
|
+
# 跨卸载的内容记忆(AST10 #2 供应链投毒对账):同名/同 id 包重装时
|
|
289
|
+
# 与上次见过的内容比对,突变即警示——卸载重装也骗不过对账。
|
|
290
|
+
seen = reg.setdefault("seen", {})
|
|
291
|
+
digest = _pack_digest(files)
|
|
292
|
+
prev_digest = str(seen.get(pack_id) or "")
|
|
293
|
+
changed_vs_seen = bool(prev_digest and prev_digest != digest)
|
|
294
|
+
# typosquatting 特征(AST10 #2):与已装技能名过近的新包点名
|
|
295
|
+
near = _similar_names(name, installed)
|
|
263
296
|
rec = installed.get(pack_id)
|
|
264
297
|
primary = "market-%s.md" % pack_id
|
|
265
298
|
target = (rec or {}).get("file") or ""
|
|
@@ -283,13 +316,24 @@ def install_files(pack_id, name, files, extra=None):
|
|
|
283
316
|
written.append(name_i)
|
|
284
317
|
except OSError as e:
|
|
285
318
|
return None, "写入用户技能库失败: %s" % e
|
|
286
|
-
record = {"file": target, "files": written, "installed_at": _now()
|
|
319
|
+
record = {"file": target, "files": written, "installed_at": _now(),
|
|
320
|
+
"digest": digest, "name": str(name or "")[:60]}
|
|
287
321
|
if scan_note:
|
|
288
322
|
record["scan"] = scan_note # 危险模式扫描结果随包记账
|
|
289
323
|
if extra:
|
|
290
324
|
record.update(extra)
|
|
291
325
|
installed[pack_id] = record
|
|
326
|
+
else:
|
|
327
|
+
installed[pack_id]["digest"] = digest
|
|
328
|
+
seen[pack_id] = digest # seen 永远跟最新一次见到对齐
|
|
292
329
|
_save_registry(reg)
|
|
330
|
+
warnings = []
|
|
331
|
+
if changed_vs_seen:
|
|
332
|
+
warnings.append("⚠ 内容较上次安装发生变化(内容指纹 %s → %s),"
|
|
333
|
+
"建议核对diff后再启用" % (prev_digest, digest))
|
|
334
|
+
for near_name, ratio in near:
|
|
335
|
+
warnings.append("⚠ 与已装技能「%s」名称过近(相似度 %.0f%%),"
|
|
336
|
+
"注意甄别仿冒(typosquatting)" % (near_name, ratio * 100))
|
|
293
337
|
# 装后冒烟验证(SkillForge 证据驱动借鉴):装完重新走 skills 的解析链,
|
|
294
338
|
# 证明该包能被加载、名字对得上、正文非空——写盘成功 ≠ 技能可用,
|
|
295
339
|
# frontmatter 缺失/正文为空都能在这里当场暴露,而不是下次任务注入时静默丢失。
|
|
@@ -317,7 +361,49 @@ def install_files(pack_id, name, files, extra=None):
|
|
|
317
361
|
reg["installed"][pack_id]["smoke"] = smoke
|
|
318
362
|
_save_registry(reg)
|
|
319
363
|
return {"ok": True, "id": pack_id, "name": name, "file": target,
|
|
320
|
-
"already": already, "scan": scan_note, "smoke": smoke
|
|
364
|
+
"already": already, "scan": scan_note, "smoke": smoke,
|
|
365
|
+
"digest": digest, "changed": changed_vs_seen,
|
|
366
|
+
"warnings": warnings}, None
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def skill_card(pack_id):
|
|
370
|
+
"""技能行为卡(ClawHub Skill Card 模式):安装时把「这个技能声明做什么 /
|
|
371
|
+
从哪来 / 指纹是什么 / 安检结论」拼成一张卡。经验库与市场卡共用这一份
|
|
372
|
+
真源——行为可见性是投毒防线的最后一环(装了什么必须随时可查)。
|
|
373
|
+
|
|
374
|
+
返回 dict 或 None(非市场安装)。"""
|
|
375
|
+
with _LOCK:
|
|
376
|
+
rec = dict((_load_registry().get("installed") or {}).get(pack_id) or {})
|
|
377
|
+
if not rec:
|
|
378
|
+
return None
|
|
379
|
+
body_head = ""
|
|
380
|
+
target = rec.get("file") or ""
|
|
381
|
+
if target:
|
|
382
|
+
try:
|
|
383
|
+
raw = (_user_pack_dir() / target).read_text(encoding="utf-8",
|
|
384
|
+
errors="replace")
|
|
385
|
+
meta, body = skills._parse_frontmatter(raw)
|
|
386
|
+
rec.setdefault("name", meta.get("name") or pack_id)
|
|
387
|
+
scopes = meta.get("scopes")
|
|
388
|
+
if isinstance(scopes, list):
|
|
389
|
+
rec.setdefault("scopes", scopes)
|
|
390
|
+
body_head = body.strip()[:200]
|
|
391
|
+
except Exception:
|
|
392
|
+
body_head = ""
|
|
393
|
+
remote = rec.get("remote") or {}
|
|
394
|
+
return {
|
|
395
|
+
"id": pack_id,
|
|
396
|
+
"name": rec.get("name") or pack_id,
|
|
397
|
+
"declares": body_head, # 技能正文开头=自述行为
|
|
398
|
+
"scopes": rec.get("scopes") or [],
|
|
399
|
+
"digest": rec.get("digest") or "",
|
|
400
|
+
"scan": rec.get("scan") or "", # 装前安检结论(含风险分级)
|
|
401
|
+
"smoke": rec.get("smoke") or "",
|
|
402
|
+
"installed_at": rec.get("installed_at") or "",
|
|
403
|
+
"source": {"market": "内置市场" if not remote else str(remote.get("name") or ""),
|
|
404
|
+
"version": str(remote.get("version") or ""),
|
|
405
|
+
"homepage": str(remote.get("homepage") or "")},
|
|
406
|
+
}
|
|
321
407
|
|
|
322
408
|
|
|
323
409
|
def remove(pack_id):
|
|
@@ -272,6 +272,9 @@ def _normalize(source_meta, entry):
|
|
|
272
272
|
"version": str(entry.get("version") or "").strip(),
|
|
273
273
|
"homepage": str(entry.get("homepage") or "").strip(),
|
|
274
274
|
"keywords": [str(k).lower() for k in (entry.get("keywords") or []) if isinstance(k, str)],
|
|
275
|
+
# 信任卡元数据(ClawTrust 思路):来源自报的下载量——自报数据只作
|
|
276
|
+
# 热度参考,不作安全结论
|
|
277
|
+
"downloads": str(((entry.get("stats") or {}).get("downloads")) or ""),
|
|
275
278
|
"source_id": source_meta["id"],
|
|
276
279
|
"source_name": source_meta["name"],
|
|
277
280
|
"install": install,
|
|
@@ -325,6 +328,8 @@ def _entries_from_cache(cache=None):
|
|
|
325
328
|
cache = cache if cache is not None else _load_cache()
|
|
326
329
|
src_meta = {s["id"]: s for s in SOURCES}
|
|
327
330
|
installed = market.installed_ids()
|
|
331
|
+
# 信任卡:已装包的安检摘要(真注册表,非 id 集合)
|
|
332
|
+
registry = market._load_registry().get("installed") or {}
|
|
328
333
|
entries = []
|
|
329
334
|
for sid in [s["id"] for s in SOURCES]:
|
|
330
335
|
c = cache.get(sid)
|
|
@@ -339,6 +344,7 @@ def _entries_from_cache(cache=None):
|
|
|
339
344
|
e["block_reason"] = _compat_block(e) or ""
|
|
340
345
|
e["installed"] = e["id"] in installed
|
|
341
346
|
e["installable"] = e["compat"] == "ok"
|
|
347
|
+
e["scan_note"] = str((registry.get(e["id"]) or {}).get("scan") or "")
|
|
342
348
|
entries.append(e)
|
|
343
349
|
entries.sort(key=lambda x: (x["source_id"], x["name"].lower()))
|
|
344
350
|
return entries
|
|
@@ -1048,6 +1054,15 @@ def preview_remote(entry_id):
|
|
|
1048
1054
|
risk_label = skill_scan.risk_label(risk_findings)
|
|
1049
1055
|
except Exception:
|
|
1050
1056
|
risk_findings, risk_label = [], ""
|
|
1057
|
+
# 供应链对账(AST10 #2):与上次见过的内容/已装名比对(只读不写)
|
|
1058
|
+
try:
|
|
1059
|
+
digest = market._pack_digest(files)
|
|
1060
|
+
reg = market._load_registry()
|
|
1061
|
+
changed = str((reg.get("seen") or {}).get(entry_id) or "") not in ("", digest)
|
|
1062
|
+
near = market._similar_names(entry.get("title") or "",
|
|
1063
|
+
reg.get("installed") or {})
|
|
1064
|
+
except Exception:
|
|
1065
|
+
digest, changed, near = "", False, []
|
|
1051
1066
|
return {
|
|
1052
1067
|
"token": token, "id": entry_id, "title": entry["title"],
|
|
1053
1068
|
"source": entry["source_name"], "version": entry["version"],
|
|
@@ -1058,6 +1073,7 @@ def preview_remote(entry_id):
|
|
|
1058
1073
|
"file_list": [{"path": k, "chars": len(v)} for k, v in file_list[:60]],
|
|
1059
1074
|
"stripped": sorted(stripped)[:40], "stripped_total": len(stripped),
|
|
1060
1075
|
"risk_label": risk_label, "risk_findings": risk_findings[:8],
|
|
1076
|
+
"digest": digest, "changed": changed, "near_names": near,
|
|
1061
1077
|
"expires_in": _PREVIEW_TTL,
|
|
1062
1078
|
}, None
|
|
1063
1079
|
except ValueError as e:
|
package/app/core/mocks.py
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""内置演示/测试智能体(mock):不消耗任何配额即可跑通全流程。"""
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import hashlib
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def _seed(agent_id):
|
|
9
|
-
return int(hashlib.sha256(agent_id.encode("utf-8")).hexdigest()[:6], 16)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def draft_manuscript(task, round_no):
|
|
13
|
-
"""生成一份示例稿件(mock 起草/修订直接落盘的内容)。"""
|
|
14
|
-
title = task.get("title") or "未命名章节"
|
|
15
|
-
goal = task.get("goal") or ""
|
|
16
|
-
return (
|
|
17
|
-
"# {title}\n\n"
|
|
18
|
-
"这是第 {r} 轮 mock 草稿(演示用,不消耗模型配额)。\n\n"
|
|
19
|
-
"## 任务目标\n{goal}\n\n"
|
|
20
|
-
"## 正文\n"
|
|
21
|
-
"夜色沿着山脊线缓慢退去,主角终于看清了石门上的刻痕——那不是文字,"
|
|
22
|
-
"而是一段被反复涂抹又反复显影的名字。他伸手触碰的瞬间,耳边响起的"
|
|
23
|
-
"却是自己昨夜说过的话。{extra}\n\n"
|
|
24
|
-
"(本轮修订关注:{focus})\n"
|
|
25
|
-
).format(
|
|
26
|
-
title=title, r=round_no, goal=goal,
|
|
27
|
-
extra="这一段在第 2 轮修订中补充了人物动机的伏笔。" if round_no >= 2 else "",
|
|
28
|
-
focus="节奏与悬念铺排" if round_no == 1 else "人物弧光与首尾呼应",
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def implement_note(task):
|
|
33
|
-
return "(mock 实现)已在目标目录写入 mock-impl.txt 作为变更占位。"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def critique(agent_id, round_no, dims, threshold):
|
|
37
|
-
"""确定性评分:第 1 轮略低于阈值,第 2 轮达标,用于验证发布门禁逻辑。"""
|
|
38
|
-
s = _seed(agent_id)
|
|
39
|
-
scores = {}
|
|
40
|
-
for i, d in enumerate(dims):
|
|
41
|
-
jitter = ((s >> i) % 5) * 0.1
|
|
42
|
-
scores[d] = round(min(9.5, 4.8 + 1.2 * round_no + jitter), 1)
|
|
43
|
-
passed = all(v >= threshold for v in scores.values())
|
|
44
|
-
issues = []
|
|
45
|
-
if round_no == 1:
|
|
46
|
-
issues = [
|
|
47
|
-
{"dim": dims[0], "severity": "major", "note": "开篇信息密度偏低,钩子出现太晚(mock 意见)"},
|
|
48
|
-
{"dim": dims[-1], "severity": "minor", "note": "段落长短交替不够,读感偏平(mock 意见)"},
|
|
49
|
-
]
|
|
50
|
-
return {
|
|
51
|
-
"scores": scores,
|
|
52
|
-
"issues": issues,
|
|
53
|
-
"summary": "mock 总评:第 %d 轮稿件%s发布阈值" % (round_no, "已达到" if passed else "尚未达到"),
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def review(task, verify_passed):
|
|
58
|
-
return {
|
|
59
|
-
"pass": bool(verify_passed),
|
|
60
|
-
"scores": {"正确性": 8.0 if verify_passed else 5.0, "可维护性": 8.0, "安全": 8.5},
|
|
61
|
-
"issues": [] if verify_passed else [
|
|
62
|
-
{"severity": "major", "title": "验证命令未通过", "detail": "请检查测试输出(mock 意见)"}],
|
|
63
|
-
"summary": "mock 代码评审:%s" % ("通过" if verify_passed else "未通过"),
|
|
64
|
-
}
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""内置演示/测试智能体(mock):不消耗任何配额即可跑通全流程。"""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _seed(agent_id):
|
|
9
|
+
return int(hashlib.sha256(agent_id.encode("utf-8")).hexdigest()[:6], 16)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def draft_manuscript(task, round_no):
|
|
13
|
+
"""生成一份示例稿件(mock 起草/修订直接落盘的内容)。"""
|
|
14
|
+
title = task.get("title") or "未命名章节"
|
|
15
|
+
goal = task.get("goal") or ""
|
|
16
|
+
return (
|
|
17
|
+
"# {title}\n\n"
|
|
18
|
+
"这是第 {r} 轮 mock 草稿(演示用,不消耗模型配额)。\n\n"
|
|
19
|
+
"## 任务目标\n{goal}\n\n"
|
|
20
|
+
"## 正文\n"
|
|
21
|
+
"夜色沿着山脊线缓慢退去,主角终于看清了石门上的刻痕——那不是文字,"
|
|
22
|
+
"而是一段被反复涂抹又反复显影的名字。他伸手触碰的瞬间,耳边响起的"
|
|
23
|
+
"却是自己昨夜说过的话。{extra}\n\n"
|
|
24
|
+
"(本轮修订关注:{focus})\n"
|
|
25
|
+
).format(
|
|
26
|
+
title=title, r=round_no, goal=goal,
|
|
27
|
+
extra="这一段在第 2 轮修订中补充了人物动机的伏笔。" if round_no >= 2 else "",
|
|
28
|
+
focus="节奏与悬念铺排" if round_no == 1 else "人物弧光与首尾呼应",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def implement_note(task):
|
|
33
|
+
return "(mock 实现)已在目标目录写入 mock-impl.txt 作为变更占位。"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def critique(agent_id, round_no, dims, threshold):
|
|
37
|
+
"""确定性评分:第 1 轮略低于阈值,第 2 轮达标,用于验证发布门禁逻辑。"""
|
|
38
|
+
s = _seed(agent_id)
|
|
39
|
+
scores = {}
|
|
40
|
+
for i, d in enumerate(dims):
|
|
41
|
+
jitter = ((s >> i) % 5) * 0.1
|
|
42
|
+
scores[d] = round(min(9.5, 4.8 + 1.2 * round_no + jitter), 1)
|
|
43
|
+
passed = all(v >= threshold for v in scores.values())
|
|
44
|
+
issues = []
|
|
45
|
+
if round_no == 1:
|
|
46
|
+
issues = [
|
|
47
|
+
{"dim": dims[0], "severity": "major", "note": "开篇信息密度偏低,钩子出现太晚(mock 意见)"},
|
|
48
|
+
{"dim": dims[-1], "severity": "minor", "note": "段落长短交替不够,读感偏平(mock 意见)"},
|
|
49
|
+
]
|
|
50
|
+
return {
|
|
51
|
+
"scores": scores,
|
|
52
|
+
"issues": issues,
|
|
53
|
+
"summary": "mock 总评:第 %d 轮稿件%s发布阈值" % (round_no, "已达到" if passed else "尚未达到"),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def review(task, verify_passed):
|
|
58
|
+
return {
|
|
59
|
+
"pass": bool(verify_passed),
|
|
60
|
+
"scores": {"正确性": 8.0 if verify_passed else 5.0, "可维护性": 8.0, "安全": 8.5},
|
|
61
|
+
"issues": [] if verify_passed else [
|
|
62
|
+
{"severity": "major", "title": "验证命令未通过", "detail": "请检查测试输出(mock 意见)"}],
|
|
63
|
+
"summary": "mock 代码评审:%s" % ("通过" if verify_passed else "未通过"),
|
|
64
|
+
}
|
package/app/core/modelhub.py
CHANGED
|
@@ -2414,8 +2414,9 @@ def _wire_endpoint_compatible(proto, base_url):
|
|
|
2414
2414
|
def bindable_protocols(agent_kind_or_id):
|
|
2415
2415
|
"""该 CLI 可绑定的 wire 协议(与 resolve_binding 的 allowed 一致)。
|
|
2416
2416
|
供死链告警/失败文案解释「为什么绑不上」:claude 只认 anthropic,
|
|
2417
|
-
codex/dsh 只认 openai,其余开放双协议(含 wire 适配)。"""
|
|
2418
|
-
if _deepseek_env_target(agent_kind_or_id)
|
|
2417
|
+
codex/dsh/qwen 只认 openai,其余开放双协议(含 wire 适配)。"""
|
|
2418
|
+
if (_deepseek_env_target(agent_kind_or_id)
|
|
2419
|
+
or agent_kind_or_id in ("codex-cli", "codex", "qwencode", "qwen")):
|
|
2419
2420
|
return ("openai",)
|
|
2420
2421
|
if agent_kind_or_id in ("claude-code", "claude"):
|
|
2421
2422
|
return ("anthropic",)
|