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.
Files changed (107) hide show
  1. package/CHANGELOG.md +589 -580
  2. package/LICENSE +21 -21
  3. package/README.md +509 -509
  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/aiflavor.py +197 -197
  13. package/app/core/artifacts.py +148 -148
  14. package/app/core/assets.py +110 -110
  15. package/app/core/attachments.py +623 -623
  16. package/app/core/audit.py +30 -30
  17. package/app/core/backup.py +470 -470
  18. package/app/core/beekeeper.py +183 -183
  19. package/app/core/benchstore.py +95 -95
  20. package/app/core/board.py +209 -209
  21. package/app/core/bookmeta.py +935 -935
  22. package/app/core/bookmeta_catalog.py +180 -180
  23. package/app/core/branching.py +137 -137
  24. package/app/core/builtin_agent.py +1673 -1673
  25. package/app/core/capability.py +144 -144
  26. package/app/core/chaptersafe.py +32 -32
  27. package/app/core/cleanup.py +303 -303
  28. package/app/core/compaction.py +201 -201
  29. package/app/core/covergen.py +356 -356
  30. package/app/core/defectretro.py +43 -43
  31. package/app/core/diagnostics.py +114 -114
  32. package/app/core/dispatch.py +190 -190
  33. package/app/core/dispatch_log.py +128 -128
  34. package/app/core/env_scrub.py +85 -85
  35. package/app/core/errorlog.py +137 -137
  36. package/app/core/evalbench.py +720 -720
  37. package/app/core/evaluation.py +174 -174
  38. package/app/core/failure_notify.py +290 -290
  39. package/app/core/flows.py +7 -7
  40. package/app/core/goal_service.py +167 -167
  41. package/app/core/health.py +374 -374
  42. package/app/core/history.py +38 -38
  43. package/app/core/hooks.py +162 -162
  44. package/app/core/manager.py +2143 -2143
  45. package/app/core/mocks.py +64 -64
  46. package/app/core/notify.py +201 -201
  47. package/app/core/novel_quality.py +189 -189
  48. package/app/core/operations.py +228 -228
  49. package/app/core/paihang.py +138 -138
  50. package/app/core/portguard.py +111 -111
  51. package/app/core/preview.py +293 -293
  52. package/app/core/publish/__init__.py +7 -7
  53. package/app/core/publish/auto.py +367 -367
  54. package/app/core/publish/browser.py +655 -655
  55. package/app/core/publish/fanqie.py +165 -165
  56. package/app/core/publish/flow.py +589 -589
  57. package/app/core/publish/flows-fanqie-calibrated.json +336 -336
  58. package/app/core/publish/ledger.py +229 -229
  59. package/app/core/publish/manager.py +824 -824
  60. package/app/core/publish/qimao.py +146 -146
  61. package/app/core/publish/ws.py +139 -139
  62. package/app/core/redact.py +38 -38
  63. package/app/core/repeat_guard.py +129 -129
  64. package/app/core/router.py +398 -398
  65. package/app/core/runner.py +2142 -2142
  66. package/app/core/selfupdate.py +384 -384
  67. package/app/core/session_log.py +161 -161
  68. package/app/core/sessions.py +312 -312
  69. package/app/core/settings.py +3 -2
  70. package/app/core/settings_schema.py +270 -270
  71. package/app/core/share_page.py +114 -114
  72. package/app/core/skill_scan.py +122 -122
  73. package/app/core/skillpacks/fanqie-novel.md +80 -80
  74. package/app/core/skillpacks/market/character-bible.md +66 -66
  75. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  76. package/app/core/skillpacks/market/git-workflow.md +57 -57
  77. package/app/core/skillpacks/market/release-notes.md +72 -72
  78. package/app/core/skillpacks/market/weekly-report.md +71 -71
  79. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  80. package/app/core/step_runner.py +132 -132
  81. package/app/core/task_compile.py +199 -199
  82. package/app/core/telemetry.py +292 -292
  83. package/app/core/tlsctx.py +64 -64
  84. package/app/core/token_meter.py +147 -147
  85. package/app/core/tracing.py +144 -144
  86. package/app/core/upstream.py +41 -41
  87. package/app/core/usage.py +1035 -1035
  88. package/app/core/volumes.py +389 -389
  89. package/app/core/wxdigest.py +858 -858
  90. package/app/core/zentao.py +2614 -2614
  91. package/app/main.py +38 -3
  92. package/app/mcp_server.py +175 -175
  93. package/app/pet.py +1 -1
  94. package/app/pick_dialog.py +110 -110
  95. package/app/ui/app.js +11 -4
  96. package/app/ui/artdiff.js +74 -74
  97. package/app/ui/board.html +162 -162
  98. package/app/ui/board.js +305 -305
  99. package/app/ui/i18n.js +1 -1
  100. package/app/ui/icons/bee.svg +79 -79
  101. package/app/ui/manifest.json +16 -16
  102. package/app/ui/qrcode.js +2297 -2297
  103. package/app/ui/style.css +65 -18
  104. package/app//344/275/234/345/223/201/344/277/241/346/201/257-/344/270/203/347/214/253.md +18 -0
  105. package/bin/tutti.js +147 -147
  106. package/package.json +47 -47
  107. 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", True)),
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", True):
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", True)),
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
  },