codebee 0.1.70 → 0.1.71
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 +589 -580
- package/LICENSE +21 -21
- package/README.md +509 -509
- package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +88 -0
- package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +126 -0
- 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 +210 -0
- 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/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 +1673 -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 +720 -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/manager.py +2143 -2143
- package/app/core/mocks.py +64 -64
- 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/portguard.py +111 -111
- package/app/core/preview.py +293 -293
- package/app/core/publish/__init__.py +7 -7
- package/app/core/publish/auto.py +367 -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 +229 -229
- package/app/core/publish/manager.py +824 -824
- package/app/core/publish/qimao.py +146 -146
- package/app/core/publish/ws.py +139 -139
- 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.py +3 -2
- package/app/core/settings_schema.py +270 -270
- package/app/core/share_page.py +114 -114
- package/app/core/skill_scan.py +122 -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/step_runner.py +132 -132
- 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 +2614 -2614
- package/app/main.py +38 -3
- package/app/mcp_server.py +175 -175
- package/app/pet.py +1 -1
- package/app/pick_dialog.py +110 -110
- package/app/ui/app.js +11 -4
- 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 +1 -1
- package/app/ui/icons/bee.svg +79 -79
- package/app/ui/manifest.json +16 -16
- package/app/ui/qrcode.js +2297 -2297
- package/app/ui/style.css +65 -18
- package/app//344/275/234/345/223/201/344/277/241/346/201/257-/344/270/203/347/214/253.md +18 -0
- package/bin/tutti.js +147 -147
- package/package.json +47 -47
- package/tools/wx_reader.py +261 -261
package/app/main.py
CHANGED
|
@@ -220,6 +220,19 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
220
220
|
|
|
221
221
|
# ------------------------------------------------------------ 路由
|
|
222
222
|
def do_GET(self):
|
|
223
|
+
# 顶层兜底:任何路由裸抛以前=socketserver 直接掐断连接、零日志,
|
|
224
|
+
# 浏览器只见 "Failed to fetch"(2026-09-29 store 局部名遮蔽实案)。
|
|
225
|
+
# 现在统一落日志并回 500 JSON;对端已断开时无从回话,原样上抛。
|
|
226
|
+
try:
|
|
227
|
+
return self._route_get()
|
|
228
|
+
except (BrokenPipeError, ConnectionAbortedError, ConnectionResetError,
|
|
229
|
+
socket.timeout):
|
|
230
|
+
raise
|
|
231
|
+
except Exception:
|
|
232
|
+
log.exception("GET %s 处理异常", getattr(self, "path", "?"))
|
|
233
|
+
self._safe_500()
|
|
234
|
+
|
|
235
|
+
def _route_get(self):
|
|
223
236
|
path = urlparse(self.path).path
|
|
224
237
|
m = None
|
|
225
238
|
# 静态页不设防(无敏感信息):远程裸地址打开时由前端令牌门引导输入
|
|
@@ -667,6 +680,24 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
667
680
|
return self._json(404, {"error": "not found"})
|
|
668
681
|
|
|
669
682
|
def do_POST(self):
|
|
683
|
+
# 同 do_GET:路由裸抛统一落日志并回 500,不再静默掐连接
|
|
684
|
+
try:
|
|
685
|
+
return self._route_post()
|
|
686
|
+
except (BrokenPipeError, ConnectionAbortedError, ConnectionResetError,
|
|
687
|
+
socket.timeout):
|
|
688
|
+
raise
|
|
689
|
+
except Exception:
|
|
690
|
+
log.exception("POST %s 处理异常", getattr(self, "path", "?"))
|
|
691
|
+
self._safe_500()
|
|
692
|
+
|
|
693
|
+
def _safe_500(self):
|
|
694
|
+
# 响应头可能已发出(流式路由半途抛),二次失败只能放弃
|
|
695
|
+
try:
|
|
696
|
+
self._json(500, {"error": "服务内部错误,详情见服务端日志"})
|
|
697
|
+
except Exception:
|
|
698
|
+
pass
|
|
699
|
+
|
|
700
|
+
def _route_post(self):
|
|
670
701
|
path = urlparse(self.path).path
|
|
671
702
|
m = None
|
|
672
703
|
if not self._authed():
|
|
@@ -918,8 +949,12 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
918
949
|
# manual=true(用户点「校准」)允许 attach-or-launch;自动触发
|
|
919
950
|
# 只 attach 在跑实例,绝不因打开页面弹出新浏览器窗口。
|
|
920
951
|
from core.publish import manager as pub
|
|
921
|
-
from core import store
|
|
922
952
|
from core.publish import ledger as pub_ledger
|
|
953
|
+
# 注意:这里绝不能再 `from core import store`——函数内 import 会把
|
|
954
|
+
# store 变成 do_POST 整个函数的局部名,同函数里其余路由(新建任务/
|
|
955
|
+
# 取消/升级全部等)一读 store 就是 UnboundLocalError,连接被静默
|
|
956
|
+
# 掐断,前端只见 "Failed to fetch"(0.1.70 实案,bisect 定位)。
|
|
957
|
+
# store 已在模块顶层导入,直接用。
|
|
923
958
|
tid = m.group(1)
|
|
924
959
|
if not store.get_task(tid):
|
|
925
960
|
return self._json(404, {"error": "任务不存在"})
|
|
@@ -1916,7 +1951,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
1916
1951
|
# 服务换了个进程(升级重启)——旧蜜蜂自行退场让新服务 spawn 新蜜蜂
|
|
1917
1952
|
# (旧形象常驻的根因:同端口轮询永远正常,旧进程从不自离)。
|
|
1918
1953
|
"boot": _BOOT_TS,
|
|
1919
|
-
"settings": {"pet_enabled": bool(st.get("pet_enabled",
|
|
1954
|
+
"settings": {"pet_enabled": bool(st.get("pet_enabled", False)),
|
|
1920
1955
|
"pet_mode": str(st.get("pet_mode") or "always"),
|
|
1921
1956
|
"pet_skin": str(st.get("pet_skin") or "plush")},
|
|
1922
1957
|
"workers": {"running": n_run, "queued": n_q},
|
|
@@ -2878,7 +2913,7 @@ def _start_pet_keeper(port):
|
|
|
2878
2913
|
def _loop():
|
|
2879
2914
|
while True:
|
|
2880
2915
|
try:
|
|
2881
|
-
if settings.load().get("pet_enabled",
|
|
2916
|
+
if settings.load().get("pet_enabled", False):
|
|
2882
2917
|
if _PET_PROC is None or _PET_PROC.poll() is not None:
|
|
2883
2918
|
# 全机唯一蜜蜂:别的服务实例(不同数据目录)已养蜂时
|
|
2884
2919
|
# 不再拉起,否则桌面会出现多只(用户实测踩坑)
|
package/app/mcp_server.py
CHANGED
|
@@ -1,175 +1,175 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""CodeBee 的 MCP 服务器形态:其他 AI 客户端(Claude Code / ZCode / Cursor)
|
|
3
|
-
把这里当工具服务器挂载,即可直接创建任务、查状态、看用量与评测榜单——
|
|
4
|
-
CodeBee 从编排工具变成可以被别的智能体调度的生态节点。
|
|
5
|
-
|
|
6
|
-
stdio JSON-RPC 2.0(按行分帧,与 core/mcp_client 同一传输约定)。启动:
|
|
7
|
-
python -X utf8 <CodeBee目录>/app/mcp_server.py
|
|
8
|
-
客户端配置示例(Claude Code / 兼容客户端):
|
|
9
|
-
{"mcpServers": {"codebee": {"command": "python",
|
|
10
|
-
"args": ["-X", "utf8", "<CodeBee目录>/app/mcp_server.py"]}}}
|
|
11
|
-
仅本机 stdio,无网络监听,凭据不经过此通道;create_task 会真实调度智能体
|
|
12
|
-
执行(与界面上建任务同一条链)。
|
|
13
|
-
"""
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
import json
|
|
17
|
-
import sys
|
|
18
|
-
|
|
19
|
-
PROTOCOL_VERSION = "2024-11-05"
|
|
20
|
-
SERVER_INFO = {"name": "codebee", "version": "1.0"}
|
|
21
|
-
|
|
22
|
-
TOOLS = [
|
|
23
|
-
{"name": "create_task", "description": "创建并启动一个 CodeBee 编排任务(真实调度智能体执行,与界面建任务同链)。返回 task_id 与 run_id。",
|
|
24
|
-
"inputSchema": {"type": "object", "properties": {
|
|
25
|
-
"goal": {"type": "string", "description": "任务目标(一句话说清要做什么)"},
|
|
26
|
-
"type": {"type": "string", "description": "任务类型 id(如 code/novel/doc/direct),默认 doc"},
|
|
27
|
-
"workdir": {"type": "string", "description": "工作目录绝对路径;留空用 CodeBee 默认保存路径"},
|
|
28
|
-
"title": {"type": "string", "description": "任务标题;留空取目标前 30 字"}},
|
|
29
|
-
"required": ["goal"]}},
|
|
30
|
-
{"name": "get_status", "description": "查任务与最近一次运行的状态:进度、当前步骤、评分、错误。",
|
|
31
|
-
"inputSchema": {"type": "object", "properties": {
|
|
32
|
-
"task_id": {"type": "string", "description": "任务 ID"}}, "required": ["task_id"]}},
|
|
33
|
-
{"name": "list_recent", "description": "列出最近的运行(id/标题/状态/时间)。",
|
|
34
|
-
"inputSchema": {"type": "object", "properties": {
|
|
35
|
-
"limit": {"type": "integer", "description": "条数,默认 10,最大 30"}}}},
|
|
36
|
-
{"name": "usage_summary", "description": "用量台账汇总:今日/本月花费(¥)与 token 总量。",
|
|
37
|
-
"inputSchema": {"type": "object", "properties": {}}},
|
|
38
|
-
{"name": "bench_leaderboard", "description": "模型评测基准台能力榜(综合分 Top N)。",
|
|
39
|
-
"inputSchema": {"type": "object", "properties": {
|
|
40
|
-
"top": {"type": "integer", "description": "取前 N,默认 5"}}}},
|
|
41
|
-
]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _enqueue(job):
|
|
45
|
-
"""thin wrapper:便于单测 mock(真链会拉起 worker 执行)。"""
|
|
46
|
-
from core import jobs
|
|
47
|
-
jobs.enqueue(job)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _tool_create_task(args):
|
|
51
|
-
from core import settings as settings_mod, store
|
|
52
|
-
goal = str(args.get("goal") or "").strip()
|
|
53
|
-
if not goal:
|
|
54
|
-
return {"isError": True, "content": "goal 不能为空"}
|
|
55
|
-
payload = {"type": str(args.get("type") or "doc").strip()[:40] or "doc",
|
|
56
|
-
"title": (str(args.get("title") or "").strip() or goal[:30])[:40],
|
|
57
|
-
"goal": goal[:4000],
|
|
58
|
-
"workdir": str(args.get("workdir") or "").strip()
|
|
59
|
-
or settings_mod.default_workdir()}
|
|
60
|
-
task = store.create_task(payload)
|
|
61
|
-
run = store.create_run("orchestration", task["title"], task_id=task["id"])
|
|
62
|
-
store.update_task_status(task["id"], "queued")
|
|
63
|
-
_enqueue({"kind": "orchestration", "run_id": run["id"], "task_id": task["id"]})
|
|
64
|
-
return {"content": "已创建并排队:task_id=%s run_id=%s(可用 get_status 查进度)"
|
|
65
|
-
% (task["id"], run["id"])}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def _tool_get_status(args):
|
|
69
|
-
from core import store
|
|
70
|
-
task = store.get_task(str(args.get("task_id") or "").strip())
|
|
71
|
-
if not task:
|
|
72
|
-
return {"isError": True, "content": "任务不存在"}
|
|
73
|
-
lines = ["任务:%s(%s)" % (task.get("title"), task.get("status"))]
|
|
74
|
-
run = store.latest_run_by_task().get(task["id"])
|
|
75
|
-
if run:
|
|
76
|
-
lines.append("最近运行:%s(%s)" % (run.get("id"), run.get("status")))
|
|
77
|
-
steps = [s for s in (run.get("steps") or []) if isinstance(s, dict)]
|
|
78
|
-
if steps:
|
|
79
|
-
cur = steps[-1]
|
|
80
|
-
lines.append("步骤 %d/%d:%s(%s)" % (len(steps), run.get("total_steps") or len(steps),
|
|
81
|
-
cur.get("title"), cur.get("status")))
|
|
82
|
-
v = run.get("verdict") or {}
|
|
83
|
-
if v.get("overall") is not None:
|
|
84
|
-
lines.append("评分 %.1f(%s)" % (float(v["overall"]),
|
|
85
|
-
"达标" if v.get("publishable") else "未达标"))
|
|
86
|
-
if run.get("error"):
|
|
87
|
-
lines.append("错误:%s" % str(run["error"])[:200])
|
|
88
|
-
if task.get("git_state") == "isolated":
|
|
89
|
-
lines.append("⚠ 任务分支待裁决(合并/丢弃)")
|
|
90
|
-
return {"content": "\n".join(lines)}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def _tool_list_recent(args):
|
|
94
|
-
from core import store
|
|
95
|
-
try:
|
|
96
|
-
limit = max(1, min(30, int(args.get("limit") or 10)))
|
|
97
|
-
except (TypeError, ValueError):
|
|
98
|
-
limit = 10
|
|
99
|
-
rows = [{"id": r.get("id"), "title": r.get("title"), "status": r.get("status"),
|
|
100
|
-
"ts": r.get("started_at") or ""} for r in store.list_runs(limit=limit)]
|
|
101
|
-
return {"content": json.dumps(rows, ensure_ascii=False, indent=1)}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def _tool_usage_summary(args):
|
|
105
|
-
from core import usage
|
|
106
|
-
today, month = usage.cost_snapshot()
|
|
107
|
-
tokens = sum(int(r.get("total") or 0) for r in usage._iter_records(31))
|
|
108
|
-
return {"content": "今日花费 ¥%.2f\n本月花费 ¥%.2f\n近 31 天 tokens:%d(未标定单价的调用不计钱)"
|
|
109
|
-
% (today, month, tokens)}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def _tool_bench(args):
|
|
113
|
-
from core import evalbench
|
|
114
|
-
try:
|
|
115
|
-
top = max(1, min(10, int(args.get("top") or 5)))
|
|
116
|
-
except (TypeError, ValueError):
|
|
117
|
-
top = 5
|
|
118
|
-
board = evalbench.state().get("leaderboard") or []
|
|
119
|
-
rows = [{"rank": r["rank"], "model": r["model"], "overall": r["overall"],
|
|
120
|
-
"provider": r.get("provider_name")} for r in board[:top]]
|
|
121
|
-
return {"content": json.dumps(rows, ensure_ascii=False, indent=1)}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
_TOOL_IMPL = {"create_task": _tool_create_task, "get_status": _tool_get_status,
|
|
125
|
-
"list_recent": _tool_list_recent, "usage_summary": _tool_usage_summary,
|
|
126
|
-
"bench_leaderboard": _tool_bench}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def handle_request(msg):
|
|
130
|
-
"""处理一条客户端请求,返回响应 dict(通知返回 None)。"""
|
|
131
|
-
if not isinstance(msg, dict) or msg.get("method") is None:
|
|
132
|
-
return None
|
|
133
|
-
rid = msg.get("id")
|
|
134
|
-
method = str(msg["method"])
|
|
135
|
-
if method == "initialize":
|
|
136
|
-
return {"jsonrpc": "2.0", "id": rid, "result": {
|
|
137
|
-
"protocolVersion": PROTOCOL_VERSION, "capabilities": {"tools": {}},
|
|
138
|
-
"serverInfo": SERVER_INFO}}
|
|
139
|
-
if method.startswith("notifications/"):
|
|
140
|
-
return None
|
|
141
|
-
if method == "tools/list":
|
|
142
|
-
return {"jsonrpc": "2.0", "id": rid, "result": {"tools": TOOLS}}
|
|
143
|
-
if method == "tools/call":
|
|
144
|
-
params = msg.get("params") or {}
|
|
145
|
-
name = str(params.get("name") or "")
|
|
146
|
-
fn = _TOOL_IMPL.get(name)
|
|
147
|
-
if fn is None:
|
|
148
|
-
return {"jsonrpc": "2.0", "id": rid, "result": {
|
|
149
|
-
"isError": True, "content": "未知工具: %s" % name}}
|
|
150
|
-
try:
|
|
151
|
-
out = fn(params.get("arguments") or {})
|
|
152
|
-
except Exception as e:
|
|
153
|
-
out = {"isError": True, "content": "工具执行失败: %s" % e}
|
|
154
|
-
return {"jsonrpc": "2.0", "id": rid, "result": out}
|
|
155
|
-
return {"jsonrpc": "2.0", "id": rid,
|
|
156
|
-
"error": {"code": -32601, "message": "no such method: %s" % method}}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def main():
|
|
160
|
-
for raw in sys.stdin:
|
|
161
|
-
line = raw.strip()
|
|
162
|
-
if not line.startswith("{"):
|
|
163
|
-
continue
|
|
164
|
-
try:
|
|
165
|
-
msg = json.loads(line)
|
|
166
|
-
except Exception:
|
|
167
|
-
continue
|
|
168
|
-
resp = handle_request(msg)
|
|
169
|
-
if resp is not None:
|
|
170
|
-
sys.stdout.write(json.dumps(resp, ensure_ascii=False) + "\n")
|
|
171
|
-
sys.stdout.flush()
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if __name__ == "__main__":
|
|
175
|
-
main()
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""CodeBee 的 MCP 服务器形态:其他 AI 客户端(Claude Code / ZCode / Cursor)
|
|
3
|
+
把这里当工具服务器挂载,即可直接创建任务、查状态、看用量与评测榜单——
|
|
4
|
+
CodeBee 从编排工具变成可以被别的智能体调度的生态节点。
|
|
5
|
+
|
|
6
|
+
stdio JSON-RPC 2.0(按行分帧,与 core/mcp_client 同一传输约定)。启动:
|
|
7
|
+
python -X utf8 <CodeBee目录>/app/mcp_server.py
|
|
8
|
+
客户端配置示例(Claude Code / 兼容客户端):
|
|
9
|
+
{"mcpServers": {"codebee": {"command": "python",
|
|
10
|
+
"args": ["-X", "utf8", "<CodeBee目录>/app/mcp_server.py"]}}}
|
|
11
|
+
仅本机 stdio,无网络监听,凭据不经过此通道;create_task 会真实调度智能体
|
|
12
|
+
执行(与界面上建任务同一条链)。
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
PROTOCOL_VERSION = "2024-11-05"
|
|
20
|
+
SERVER_INFO = {"name": "codebee", "version": "1.0"}
|
|
21
|
+
|
|
22
|
+
TOOLS = [
|
|
23
|
+
{"name": "create_task", "description": "创建并启动一个 CodeBee 编排任务(真实调度智能体执行,与界面建任务同链)。返回 task_id 与 run_id。",
|
|
24
|
+
"inputSchema": {"type": "object", "properties": {
|
|
25
|
+
"goal": {"type": "string", "description": "任务目标(一句话说清要做什么)"},
|
|
26
|
+
"type": {"type": "string", "description": "任务类型 id(如 code/novel/doc/direct),默认 doc"},
|
|
27
|
+
"workdir": {"type": "string", "description": "工作目录绝对路径;留空用 CodeBee 默认保存路径"},
|
|
28
|
+
"title": {"type": "string", "description": "任务标题;留空取目标前 30 字"}},
|
|
29
|
+
"required": ["goal"]}},
|
|
30
|
+
{"name": "get_status", "description": "查任务与最近一次运行的状态:进度、当前步骤、评分、错误。",
|
|
31
|
+
"inputSchema": {"type": "object", "properties": {
|
|
32
|
+
"task_id": {"type": "string", "description": "任务 ID"}}, "required": ["task_id"]}},
|
|
33
|
+
{"name": "list_recent", "description": "列出最近的运行(id/标题/状态/时间)。",
|
|
34
|
+
"inputSchema": {"type": "object", "properties": {
|
|
35
|
+
"limit": {"type": "integer", "description": "条数,默认 10,最大 30"}}}},
|
|
36
|
+
{"name": "usage_summary", "description": "用量台账汇总:今日/本月花费(¥)与 token 总量。",
|
|
37
|
+
"inputSchema": {"type": "object", "properties": {}}},
|
|
38
|
+
{"name": "bench_leaderboard", "description": "模型评测基准台能力榜(综合分 Top N)。",
|
|
39
|
+
"inputSchema": {"type": "object", "properties": {
|
|
40
|
+
"top": {"type": "integer", "description": "取前 N,默认 5"}}}},
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _enqueue(job):
|
|
45
|
+
"""thin wrapper:便于单测 mock(真链会拉起 worker 执行)。"""
|
|
46
|
+
from core import jobs
|
|
47
|
+
jobs.enqueue(job)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _tool_create_task(args):
|
|
51
|
+
from core import settings as settings_mod, store
|
|
52
|
+
goal = str(args.get("goal") or "").strip()
|
|
53
|
+
if not goal:
|
|
54
|
+
return {"isError": True, "content": "goal 不能为空"}
|
|
55
|
+
payload = {"type": str(args.get("type") or "doc").strip()[:40] or "doc",
|
|
56
|
+
"title": (str(args.get("title") or "").strip() or goal[:30])[:40],
|
|
57
|
+
"goal": goal[:4000],
|
|
58
|
+
"workdir": str(args.get("workdir") or "").strip()
|
|
59
|
+
or settings_mod.default_workdir()}
|
|
60
|
+
task = store.create_task(payload)
|
|
61
|
+
run = store.create_run("orchestration", task["title"], task_id=task["id"])
|
|
62
|
+
store.update_task_status(task["id"], "queued")
|
|
63
|
+
_enqueue({"kind": "orchestration", "run_id": run["id"], "task_id": task["id"]})
|
|
64
|
+
return {"content": "已创建并排队:task_id=%s run_id=%s(可用 get_status 查进度)"
|
|
65
|
+
% (task["id"], run["id"])}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _tool_get_status(args):
|
|
69
|
+
from core import store
|
|
70
|
+
task = store.get_task(str(args.get("task_id") or "").strip())
|
|
71
|
+
if not task:
|
|
72
|
+
return {"isError": True, "content": "任务不存在"}
|
|
73
|
+
lines = ["任务:%s(%s)" % (task.get("title"), task.get("status"))]
|
|
74
|
+
run = store.latest_run_by_task().get(task["id"])
|
|
75
|
+
if run:
|
|
76
|
+
lines.append("最近运行:%s(%s)" % (run.get("id"), run.get("status")))
|
|
77
|
+
steps = [s for s in (run.get("steps") or []) if isinstance(s, dict)]
|
|
78
|
+
if steps:
|
|
79
|
+
cur = steps[-1]
|
|
80
|
+
lines.append("步骤 %d/%d:%s(%s)" % (len(steps), run.get("total_steps") or len(steps),
|
|
81
|
+
cur.get("title"), cur.get("status")))
|
|
82
|
+
v = run.get("verdict") or {}
|
|
83
|
+
if v.get("overall") is not None:
|
|
84
|
+
lines.append("评分 %.1f(%s)" % (float(v["overall"]),
|
|
85
|
+
"达标" if v.get("publishable") else "未达标"))
|
|
86
|
+
if run.get("error"):
|
|
87
|
+
lines.append("错误:%s" % str(run["error"])[:200])
|
|
88
|
+
if task.get("git_state") == "isolated":
|
|
89
|
+
lines.append("⚠ 任务分支待裁决(合并/丢弃)")
|
|
90
|
+
return {"content": "\n".join(lines)}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _tool_list_recent(args):
|
|
94
|
+
from core import store
|
|
95
|
+
try:
|
|
96
|
+
limit = max(1, min(30, int(args.get("limit") or 10)))
|
|
97
|
+
except (TypeError, ValueError):
|
|
98
|
+
limit = 10
|
|
99
|
+
rows = [{"id": r.get("id"), "title": r.get("title"), "status": r.get("status"),
|
|
100
|
+
"ts": r.get("started_at") or ""} for r in store.list_runs(limit=limit)]
|
|
101
|
+
return {"content": json.dumps(rows, ensure_ascii=False, indent=1)}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _tool_usage_summary(args):
|
|
105
|
+
from core import usage
|
|
106
|
+
today, month = usage.cost_snapshot()
|
|
107
|
+
tokens = sum(int(r.get("total") or 0) for r in usage._iter_records(31))
|
|
108
|
+
return {"content": "今日花费 ¥%.2f\n本月花费 ¥%.2f\n近 31 天 tokens:%d(未标定单价的调用不计钱)"
|
|
109
|
+
% (today, month, tokens)}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _tool_bench(args):
|
|
113
|
+
from core import evalbench
|
|
114
|
+
try:
|
|
115
|
+
top = max(1, min(10, int(args.get("top") or 5)))
|
|
116
|
+
except (TypeError, ValueError):
|
|
117
|
+
top = 5
|
|
118
|
+
board = evalbench.state().get("leaderboard") or []
|
|
119
|
+
rows = [{"rank": r["rank"], "model": r["model"], "overall": r["overall"],
|
|
120
|
+
"provider": r.get("provider_name")} for r in board[:top]]
|
|
121
|
+
return {"content": json.dumps(rows, ensure_ascii=False, indent=1)}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
_TOOL_IMPL = {"create_task": _tool_create_task, "get_status": _tool_get_status,
|
|
125
|
+
"list_recent": _tool_list_recent, "usage_summary": _tool_usage_summary,
|
|
126
|
+
"bench_leaderboard": _tool_bench}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def handle_request(msg):
|
|
130
|
+
"""处理一条客户端请求,返回响应 dict(通知返回 None)。"""
|
|
131
|
+
if not isinstance(msg, dict) or msg.get("method") is None:
|
|
132
|
+
return None
|
|
133
|
+
rid = msg.get("id")
|
|
134
|
+
method = str(msg["method"])
|
|
135
|
+
if method == "initialize":
|
|
136
|
+
return {"jsonrpc": "2.0", "id": rid, "result": {
|
|
137
|
+
"protocolVersion": PROTOCOL_VERSION, "capabilities": {"tools": {}},
|
|
138
|
+
"serverInfo": SERVER_INFO}}
|
|
139
|
+
if method.startswith("notifications/"):
|
|
140
|
+
return None
|
|
141
|
+
if method == "tools/list":
|
|
142
|
+
return {"jsonrpc": "2.0", "id": rid, "result": {"tools": TOOLS}}
|
|
143
|
+
if method == "tools/call":
|
|
144
|
+
params = msg.get("params") or {}
|
|
145
|
+
name = str(params.get("name") or "")
|
|
146
|
+
fn = _TOOL_IMPL.get(name)
|
|
147
|
+
if fn is None:
|
|
148
|
+
return {"jsonrpc": "2.0", "id": rid, "result": {
|
|
149
|
+
"isError": True, "content": "未知工具: %s" % name}}
|
|
150
|
+
try:
|
|
151
|
+
out = fn(params.get("arguments") or {})
|
|
152
|
+
except Exception as e:
|
|
153
|
+
out = {"isError": True, "content": "工具执行失败: %s" % e}
|
|
154
|
+
return {"jsonrpc": "2.0", "id": rid, "result": out}
|
|
155
|
+
return {"jsonrpc": "2.0", "id": rid,
|
|
156
|
+
"error": {"code": -32601, "message": "no such method: %s" % method}}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def main():
|
|
160
|
+
for raw in sys.stdin:
|
|
161
|
+
line = raw.strip()
|
|
162
|
+
if not line.startswith("{"):
|
|
163
|
+
continue
|
|
164
|
+
try:
|
|
165
|
+
msg = json.loads(line)
|
|
166
|
+
except Exception:
|
|
167
|
+
continue
|
|
168
|
+
resp = handle_request(msg)
|
|
169
|
+
if resp is not None:
|
|
170
|
+
sys.stdout.write(json.dumps(resp, ensure_ascii=False) + "\n")
|
|
171
|
+
sys.stdout.flush()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
main()
|
package/app/pet.py
CHANGED
|
@@ -89,7 +89,7 @@ def parse_snapshot(raw):
|
|
|
89
89
|
# boot:服务进程启动标识(值变化=服务换人,本宠让位给新服务的蜜蜂)
|
|
90
90
|
"boot": raw.get("boot") or None,
|
|
91
91
|
"settings": {
|
|
92
|
-
"pet_enabled": bool(st.get("pet_enabled",
|
|
92
|
+
"pet_enabled": bool(st.get("pet_enabled", False)),
|
|
93
93
|
"pet_mode": str(st.get("pet_mode") or "always"),
|
|
94
94
|
"pet_skin": str(st.get("pet_skin") or DEFAULT_SKIN),
|
|
95
95
|
},
|