codebee 0.1.4 → 0.1.6
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 +44 -0
- package/README.md +8 -0
- package/app/core/attachments.py +40 -0
- package/app/core/bookmeta.py +157 -52
- package/app/core/bookmeta_catalog.py +67 -90
- package/app/core/builtin_agent.py +190 -8
- package/app/core/flows.py +328 -328
- package/app/core/gitmod.py +82 -8
- package/app/core/health.py +55 -1
- package/app/core/history.py +8 -2
- package/app/core/jobs.py +19 -4
- package/app/core/manager.py +159 -1
- package/app/core/modelhub.py +148 -15
- package/app/core/pipeline.py +2392 -2267
- package/app/core/registry.py +4 -0
- package/app/core/router.py +13 -7
- package/app/core/runner.py +194 -21
- package/app/core/selfupdate.py +60 -11
- package/app/core/store.py +4 -2
- package/app/main.py +101 -7
- package/app/ui/app.js +447 -115
- package/app/ui/i18n.js +32 -10
- package/app/ui/icons/bee.svg +79 -0
- package/app/ui/index.html +8 -3
- package/app/ui/style.css +3226 -2764
- package/package.json +2 -1
package/app/main.py
CHANGED
|
@@ -25,6 +25,8 @@ from core import health
|
|
|
25
25
|
|
|
26
26
|
MIME = {".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8",
|
|
27
27
|
".css": "text/css; charset=utf-8", ".svg": "image/svg+xml", ".png": "image/png",
|
|
28
|
+
".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif",
|
|
29
|
+
".webp": "image/webp", ".bmp": "image/bmp",
|
|
28
30
|
".ico": "image/x-icon", ".json": "application/manifest+json; charset=utf-8",
|
|
29
31
|
".webmanifest": "application/manifest+json; charset=utf-8"}
|
|
30
32
|
|
|
@@ -320,6 +322,21 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
320
322
|
m = re.match(r"^/api/runs/([^/]+)/timeline$", path)
|
|
321
323
|
if m:
|
|
322
324
|
return self._api_run_timeline(m.group(1))
|
|
325
|
+
m = re.match(r"^/api/attachments/([0-9a-f]{16})$", path)
|
|
326
|
+
if m:
|
|
327
|
+
# 待提交附件原文(对话输入条胶囊点击预览):只读,id 猜不中即 404
|
|
328
|
+
from core import attachments
|
|
329
|
+
data, mime, name = attachments.read_pending(m.group(1))
|
|
330
|
+
if not data:
|
|
331
|
+
return self._json(404, {"error": "附件不存在或已提交"})
|
|
332
|
+
ext = Path(name.lower()).suffix
|
|
333
|
+
ctype = MIME.get(ext, mime or "application/octet-stream")
|
|
334
|
+
if ctype.startswith("text/") or ext in (
|
|
335
|
+
".txt", ".md", ".log", ".csv", ".json", ".xml",
|
|
336
|
+
".yml", ".yaml", ".toml", ".py", ".js", ".ts", ".html", ".css"):
|
|
337
|
+
ctype = "text/plain; charset=utf-8"
|
|
338
|
+
data = _utf8_bytes(data)
|
|
339
|
+
return self._send(200, data, ctype)
|
|
323
340
|
if path == "/api/automation":
|
|
324
341
|
return self._json(200, {"tasks": automation.list_tasks(),
|
|
325
342
|
"templates": automation.templates()})
|
|
@@ -572,6 +589,14 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
572
589
|
err = modelhub.reorder_models(body.get("provider_id") or "",
|
|
573
590
|
body.get("names") or [])
|
|
574
591
|
return self._json(400, {"error": err}) if err else self._json(200, {"ok": True})
|
|
592
|
+
if path == "/api/models/model-caps":
|
|
593
|
+
# 模态能力声明:当前仅 image_in(图片输入),内置智能体传图以此为准
|
|
594
|
+
from core import modelhub
|
|
595
|
+
body = self._body()
|
|
596
|
+
err = modelhub.set_model_caps(body.get("provider_id") or "",
|
|
597
|
+
body.get("name") or "",
|
|
598
|
+
body.get("image_in"))
|
|
599
|
+
return self._json(400, {"error": err}) if err else self._json(200, {"ok": True})
|
|
575
600
|
if path == "/api/models/test-provider":
|
|
576
601
|
from core import modelhub
|
|
577
602
|
return self._json(200, modelhub.test_provider(self._body().get("id") or ""))
|
|
@@ -1122,16 +1147,51 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
1122
1147
|
return self._json(200, {"ok": True, "message": msg})
|
|
1123
1148
|
|
|
1124
1149
|
def _api_run_timeline(self, run_id):
|
|
1125
|
-
"""
|
|
1150
|
+
"""直连对话视图的时间线:目标 + 用户消息 + 各步最终回答按序合并成气泡流。
|
|
1126
1151
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1152
|
+
形状:[任务目标] → [用户消息] → [assistant 输出] → [用户追问] → …。
|
|
1153
|
+
步骤正文只认 output/summary(干净回答),供详情页「对话」分区直读。
|
|
1154
|
+
|
|
1155
|
+
直连任务按「任务级」回放(跨该任务全部 run 依时间合并):追话每轮
|
|
1156
|
+
起一个新 run,用户消息有继承、智能体回答(步骤 output)不继承——只
|
|
1157
|
+
回放单个 run 会把历史轮的回答全丢掉(2026-09-17 实测:一发消息,
|
|
1158
|
+
之前智能体输出从时间线消失)。继承的消息是同 id 副本,跨 run 按
|
|
1159
|
+
(id, 时间, 文本) 去重;每个 run 内先消息后步骤(消息总是先于本轮
|
|
1160
|
+
回答送达)。非 direct 任务保持单 run 步骤流(视图未启用)。"""
|
|
1130
1161
|
run = store.get_run(run_id)
|
|
1131
1162
|
if not run:
|
|
1132
1163
|
return self._json(404, {"error": "not found"})
|
|
1164
|
+
task = store.get_task(run.get("task_id") or "") if run.get("task_id") else None
|
|
1165
|
+
engine = (task or {}).get("engine") or ""
|
|
1133
1166
|
items = []
|
|
1134
|
-
|
|
1167
|
+
if engine == "direct":
|
|
1168
|
+
runs = list(reversed(store.task_runs(run["task_id"]))) # 旧→新
|
|
1169
|
+
else:
|
|
1170
|
+
runs = [run]
|
|
1171
|
+
# 首条用户气泡=任务目标:否则对话开场只有智能体在说话(用户看不到自己问了什么)。
|
|
1172
|
+
# 附件归一成 _attachments/ 相对路径:前端展示仍取文件名(attName 剥目录),
|
|
1173
|
+
# 但「点击查看」需要相对路径走 /api/runs/<id>/file 通道——只下发文件名的话
|
|
1174
|
+
# 点开永远 404。时间取任务创建时刻(对话的开场是任务本身,不是某一轮续跑的起始时间)
|
|
1175
|
+
if task and engine == "direct":
|
|
1176
|
+
from core import attachments as _att
|
|
1177
|
+
atts = [p for p in (_att.norm_rel(a) for a in (task.get("attachments") or [])) if p]
|
|
1178
|
+
items.append({
|
|
1179
|
+
"kind": "user",
|
|
1180
|
+
"at": task.get("created_at") or (runs[0].get("created_at") if runs else "") or "",
|
|
1181
|
+
"who": "用户",
|
|
1182
|
+
"text": task.get("goal") or "",
|
|
1183
|
+
"attachments": atts,
|
|
1184
|
+
"consumed": True,
|
|
1185
|
+
"id": None,
|
|
1186
|
+
})
|
|
1187
|
+
seen_msgs = set()
|
|
1188
|
+
|
|
1189
|
+
def _emit_msg(m):
|
|
1190
|
+
key = (str(m.get("id") or ""), str(m.get("created_at") or ""),
|
|
1191
|
+
str(m.get("text") or ""))
|
|
1192
|
+
if key in seen_msgs:
|
|
1193
|
+
return # retry 继承的同一条消息:只在原 run 位置显示一次
|
|
1194
|
+
seen_msgs.add(key)
|
|
1135
1195
|
items.append({
|
|
1136
1196
|
"kind": "user",
|
|
1137
1197
|
"at": m.get("created_at") or "",
|
|
@@ -1141,7 +1201,8 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
1141
1201
|
"consumed": bool(m.get("consumed")),
|
|
1142
1202
|
"id": m.get("id"),
|
|
1143
1203
|
})
|
|
1144
|
-
|
|
1204
|
+
|
|
1205
|
+
def _emit_step(s):
|
|
1145
1206
|
# 正文只认 output(runner 抽好的最终回答)→ summary。绝不回退读原始
|
|
1146
1207
|
# 日志:日志是全量事件流(下发提示词回显 + CLI 报错),塞进气泡就成了
|
|
1147
1208
|
# 「看日志」(2026-09-17 实测症状);运行中的步骤两者都还没有,留空给
|
|
@@ -1159,9 +1220,29 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
1159
1220
|
"text": body,
|
|
1160
1221
|
"note": s.get("note") or "",
|
|
1161
1222
|
})
|
|
1223
|
+
|
|
1224
|
+
for r in runs:
|
|
1225
|
+
msgs = r.get("messages") or []
|
|
1226
|
+
steps = r.get("steps") or []
|
|
1227
|
+
# 消息没有日期(只有 HH:MM:SS),跨 run 不能按时间混排;用语义顺序:
|
|
1228
|
+
# 已消费消息(驱动了本轮回答)→ 已完成步骤 → 未消费消息(run 结束后
|
|
1229
|
+
# 才追话到达的)→ 运行中/排队步骤(打字动画)。追问因此总在上一轮
|
|
1230
|
+
# 回答之后、本轮打字动画之前。
|
|
1231
|
+
for m in msgs:
|
|
1232
|
+
if m.get("consumed"):
|
|
1233
|
+
_emit_msg(m)
|
|
1234
|
+
for s in steps:
|
|
1235
|
+
if (s.get("status") or "") == "done":
|
|
1236
|
+
_emit_step(s)
|
|
1237
|
+
for m in msgs:
|
|
1238
|
+
if not m.get("consumed"):
|
|
1239
|
+
_emit_msg(m)
|
|
1240
|
+
for s in steps:
|
|
1241
|
+
if (s.get("status") or "") != "done":
|
|
1242
|
+
_emit_step(s)
|
|
1162
1243
|
return self._json(200, {
|
|
1163
1244
|
"run_id": run_id, "status": run.get("status") or "",
|
|
1164
|
-
"engine":
|
|
1245
|
+
"engine": engine,
|
|
1165
1246
|
"items": items,
|
|
1166
1247
|
})
|
|
1167
1248
|
|
|
@@ -1389,6 +1470,19 @@ def main():
|
|
|
1389
1470
|
n_rc = store.recover_orphaned_runs()
|
|
1390
1471
|
if n_rc:
|
|
1391
1472
|
print("[CodeBee] 崩溃恢复:%d 个遗留运行标记为 failed(interrupted at startup)" % n_rc)
|
|
1473
|
+
try:
|
|
1474
|
+
from core import manager as _mgr
|
|
1475
|
+
n_z = _mgr.sweep_orphan_cli_processes()
|
|
1476
|
+
if n_z:
|
|
1477
|
+
# 服务重启孤儿化的 CLI 孙进程:僵尸 opencode 会劫持后续会话的项目根,
|
|
1478
|
+
# 必须在恢复运行之前清掉(2026-09-17 mo-so「工作目录是 Temp」真凶)
|
|
1479
|
+
print("[CodeBee] 崩溃恢复:清扫 %d 个孤儿 CLI 进程(opencode/codex)" % n_z)
|
|
1480
|
+
except Exception:
|
|
1481
|
+
pass
|
|
1482
|
+
from core import bookmeta
|
|
1483
|
+
n_bo = bookmeta.recover_orphans() # 作品信息生成线程同样会被重启杀掉,遗留 running 收尸
|
|
1484
|
+
if n_bo:
|
|
1485
|
+
print("[CodeBee] 崩溃恢复:%d 条作品信息生成中断标记为 failed(可点重试)" % n_bo)
|
|
1392
1486
|
try:
|
|
1393
1487
|
from core import settings_schema
|
|
1394
1488
|
settings_schema.register_default_namespaces() # budget/cascade/compaction 配置就绪(幂等)
|