codebee 0.1.69 → 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 -543
- package/LICENSE +21 -21
- package/README.md +509 -508
- 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/browser-final-1440.png +0 -0
- package/app/browser-final-390.png +0 -0
- package/app/browser-google-fallback.png +0 -0
- package/app/browser-google-test.png +0 -0
- package/app/browser-workbench-1440.png +0 -0
- package/app/browser-workbench-390.png +0 -0
- package/app/browser-workspace-google.png +0 -0
- package/app/browser-workspace-smoke.png +0 -0
- package/app/browser-workspace.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 -0
- package/app/core/automation.py +107 -12
- package/app/core/backup.py +470 -470
- package/app/core/beekeeper.py +183 -183
- package/app/core/benchstore.py +95 -0
- package/app/core/board.py +209 -209
- package/app/core/bookmeta.py +935 -930
- package/app/core/bookmeta_catalog.py +180 -180
- package/app/core/branching.py +137 -137
- package/app/core/builtin_agent.py +1673 -1234
- package/app/core/capability.py +144 -144
- package/app/core/catalog.py +7 -1
- 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 -173
- package/app/core/dispatch_log.py +128 -128
- package/app/core/env_scrub.py +85 -85
- package/app/core/error_codes.py +11 -2
- package/app/core/errorlog.py +137 -137
- package/app/core/evalbench.py +720 -0
- package/app/core/evaluation.py +174 -174
- package/app/core/failure_notify.py +290 -290
- package/app/core/flows.py +234 -9
- 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 +22 -1
- package/app/core/manager.py +2143 -2122
- package/app/core/market_remote.py +10 -0
- package/app/core/mcp_client.py +287 -0
- package/app/core/mocks.py +64 -64
- package/app/core/modelhub.py +185 -2
- package/app/core/notify.py +201 -106
- package/app/core/novel_quality.py +189 -0
- package/app/core/operations.py +228 -228
- package/app/core/paihang.py +138 -138
- package/app/core/paths.py +4 -0
- package/app/core/pipeline.py +426 -28
- package/app/core/planner.py +243 -22
- package/app/core/plugins.py +525 -0
- 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 -539
- package/app/core/publish/fanqie.py +165 -159
- package/app/core/publish/flow.py +589 -440
- package/app/core/publish/flows-fanqie-calibrated.json +336 -332
- package/app/core/publish/ledger.py +229 -203
- package/app/core/publish/manager.py +824 -692
- package/app/core/publish/qimao.py +146 -141
- package/app/core/publish/ws.py +139 -139
- package/app/core/redact.py +38 -38
- package/app/core/remotesync.py +121 -0
- package/app/core/repeat_guard.py +129 -129
- package/app/core/router.py +398 -398
- package/app/core/runner.py +2142 -2067
- 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 +42 -4
- package/app/core/settings_schema.py +270 -262
- 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/store.py +30 -8
- 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 -862
- package/app/core/volumes.py +389 -389
- package/app/core/wxdigest.py +858 -858
- package/app/core/zentao.py +2614 -2465
- package/app/current-browser.png +0 -0
- package/app/main.py +316 -17
- package/app/mcp_server.py +175 -0
- package/app/pet.py +83 -22
- package/app/pick_dialog.py +110 -110
- package/app/plugins/code-review-helper/.codex-plugin/plugin.json +18 -0
- package/app/plugins/code-review-helper/skills/review.md +16 -0
- package/app/ui/app.js +1391 -77
- 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 +249 -7
- package/app/ui/icons/bee.svg +79 -79
- package/app/ui/index.html +277 -13
- package/app/ui/manifest.json +16 -16
- package/app/ui/qrcode.js +2297 -2297
- package/app/ui/style.css +845 -12
- 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 -46
- package/tools/wx_reader.py +261 -261
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""轻量审计日志:不可逆操作留痕(当前仅任务删除)。
|
|
3
|
+
|
|
4
|
+
删除任务连运行记录一起物理清除且无回收站,出争议时无据可查
|
|
5
|
+
(2026-09-28 连载根任务被删案:谁删的、何时删的全无线索)。
|
|
6
|
+
这里按月追加 jsonl 到 data/audit/,一行一对象、正序追加,
|
|
7
|
+
与用量台账同款纪律。审计失败绝不拖垮主流程。
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import time
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from . import paths
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def record(event: str, detail: dict) -> None:
|
|
19
|
+
"""追加一条审计记录(event 即台账名,如 task_delete)。"""
|
|
20
|
+
try:
|
|
21
|
+
day = time.strftime("%Y-%m")
|
|
22
|
+
d = Path(paths.DATA_DIR) / "audit"
|
|
23
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
line = json.dumps(
|
|
25
|
+
{"ts": time.strftime("%Y-%m-%d %H:%M:%S"), "event": event, **detail},
|
|
26
|
+
ensure_ascii=False)
|
|
27
|
+
with open(d / ("%s-%s.jsonl" % (event, day)), "a", encoding="utf-8") as f:
|
|
28
|
+
f.write(line + "\n")
|
|
29
|
+
except Exception:
|
|
30
|
+
pass
|
package/app/core/automation.py
CHANGED
|
@@ -3,19 +3,18 @@
|
|
|
3
3
|
|
|
4
4
|
数据落盘 <data>/automation.json(tmp + os.replace 原子写;data 目录由 paths.py
|
|
5
5
|
决定,TUTTI_DATA 环境变量感知)。调度是一个 daemon 线程,每 TICK_SECONDS 秒
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
扫一遍到期任务:首次到点按 /api/tasks 的链路创建一个持久任务,后续触发复用
|
|
7
|
+
该任务并只新增 run 记录,再交给 jobs.enqueue 拉起真实运行。单次触发
|
|
8
8
|
失败只把 last_status 记为 error 并推进 next_run,调度线程绝不允许因异常退出。
|
|
9
9
|
|
|
10
10
|
任务模型:
|
|
11
11
|
{id, name, prompt, workdir, kind: daily|interval|weekly|once, time: "HH:MM",
|
|
12
12
|
interval_hours, weekday(0-6 周一=0), run_at(once 用, ISO), flow(编排流程 id),
|
|
13
13
|
mode(编排模式), thinking(思考程度), direct_provider_id/direct_model(仅 direct 流程),
|
|
14
|
-
enabled, created_at, last_run, next_run, run_count, last_status}
|
|
14
|
+
enabled, created_at, last_run, next_run, run_count, last_status, task_id}
|
|
15
15
|
|
|
16
16
|
运行偏好(mode/thinking/对话模型)与新建任务 Composer 的三颗胶囊同一套取值,
|
|
17
|
-
|
|
18
|
-
选的编排强度与模型偏好必须与手动建任务完全等价。
|
|
17
|
+
首次创建时原样透传给 store.create_task;配置身份变化时才建立新的任务上下文。
|
|
19
18
|
|
|
20
19
|
重启语义:错过的 once 不补跑(启动恢复时直接停用);daily/weekly/interval
|
|
21
20
|
重算 next_run 到下一个未来时刻即可,不追赶停机期间错过的周期。
|
|
@@ -43,11 +42,24 @@ from . import jobs, paths, store
|
|
|
43
42
|
log = logging.getLogger(__name__)
|
|
44
43
|
|
|
45
44
|
_LOCK = threading.RLock()
|
|
45
|
+
# Scheduler ticks and the manual "run now" action can arrive concurrently.
|
|
46
|
+
# Keep the active-run check and enqueue decision single-flight per process.
|
|
47
|
+
_LAUNCH_LOCK = threading.RLock()
|
|
46
48
|
_FILE = paths.DATA_DIR / "automation.json"
|
|
47
49
|
_TASKS = {} # id → task dict(内存真源;落盘为 {"version":1,"tasks":[全部任务]})
|
|
48
50
|
_LOADED = False
|
|
49
51
|
_STARTED = False
|
|
50
52
|
|
|
53
|
+
# 模块外 tick 钩子(main.py 启动注入,如 evalbench.fire_due 定时回归):
|
|
54
|
+
# automation 不点名任何业务模块——注入方向 main(L3)→钩子,本模块静态图保持干净
|
|
55
|
+
_TICK_HOOKS = []
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def register_tick_hook(fn):
|
|
59
|
+
"""注册一个每 tick 调用的零参函数(自节流,异常自行吞掉)。"""
|
|
60
|
+
if callable(fn) and fn not in _TICK_HOOKS:
|
|
61
|
+
_TICK_HOOKS.append(fn)
|
|
62
|
+
|
|
51
63
|
TICK_SECONDS = 25 # 调度扫描间隔:到点触发误差不超过半个周期
|
|
52
64
|
KINDS = ("daily", "interval", "weekly", "once")
|
|
53
65
|
INTERVAL_MIN, INTERVAL_MAX = 1, 720 # interval_hours 合法区间(小时)
|
|
@@ -65,7 +77,7 @@ _DEFAULTS = {"id": "", "name": "", "prompt": "", "workdir": "", "kind": "", "tim
|
|
|
65
77
|
"mode": "auto", "thinking": "standard",
|
|
66
78
|
"direct_provider_id": "", "direct_model": "",
|
|
67
79
|
"enabled": False, "created_at": "", "last_run": "", "next_run": "",
|
|
68
|
-
"run_count": 0, "last_status": "", "last_run_id": ""}
|
|
80
|
+
"run_count": 0, "last_status": "", "last_run_id": "", "task_id": ""}
|
|
69
81
|
|
|
70
82
|
# 允许通过 update() 修改的字段(id/created_at/run_count 等运行痕迹不可改)
|
|
71
83
|
_UPDATABLE = ("name", "prompt", "workdir", "kind", "time", "interval_hours",
|
|
@@ -277,6 +289,7 @@ def _normalize(d):
|
|
|
277
289
|
except (TypeError, ValueError):
|
|
278
290
|
t["run_count"] = 0
|
|
279
291
|
t["enabled"] = bool(t["enabled"])
|
|
292
|
+
t["task_id"] = str(t.get("task_id") or "").strip()
|
|
280
293
|
return t
|
|
281
294
|
|
|
282
295
|
|
|
@@ -333,6 +346,60 @@ def _recover_after_restart():
|
|
|
333
346
|
|
|
334
347
|
# ---------------------------------------------------------------- 运行拉起
|
|
335
348
|
|
|
349
|
+
|
|
350
|
+
class _AutomationBusy(RuntimeError):
|
|
351
|
+
"""The persistent task already has a queued or running run."""
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _active_run(task_id):
|
|
355
|
+
"""Return an active run for a persistent task, if any."""
|
|
356
|
+
try:
|
|
357
|
+
for run in store.task_runs(task_id):
|
|
358
|
+
if run.get("status") in ("queued", "running"):
|
|
359
|
+
return run
|
|
360
|
+
except Exception:
|
|
361
|
+
# Fail closed: if the run index cannot be read, do not risk launching
|
|
362
|
+
# a duplicate run against the same persistent task.
|
|
363
|
+
log.debug("automation: active-run check failed task=%s", task_id,
|
|
364
|
+
exc_info=True)
|
|
365
|
+
return True
|
|
366
|
+
return None
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _effective_workdir(value):
|
|
370
|
+
"""Resolve an automation workdir the same way store.create_task does."""
|
|
371
|
+
requested = str(value or "").strip()
|
|
372
|
+
if not requested:
|
|
373
|
+
from . import settings
|
|
374
|
+
requested = settings.default_workdir()
|
|
375
|
+
return os.path.normcase(os.path.abspath(os.path.expanduser(requested)))
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _persistent_task_compatible(task, payload):
|
|
379
|
+
"""Only reuse a backing task while its execution identity is unchanged."""
|
|
380
|
+
if not task or task.get("type") != payload.get("type"):
|
|
381
|
+
return False
|
|
382
|
+
if task.get("goal") != payload.get("goal"):
|
|
383
|
+
return False
|
|
384
|
+
if _effective_workdir(task.get("workdir")) != _effective_workdir(payload.get("workdir")):
|
|
385
|
+
return False
|
|
386
|
+
for key in ("mode", "thinking", "direct_provider_id", "direct_model"):
|
|
387
|
+
if (task.get(key) or "") != (payload.get(key) or ""):
|
|
388
|
+
return False
|
|
389
|
+
return True
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _bind_task_id(automation_id, task_id):
|
|
393
|
+
"""Persist the CodeBee task backing one schedule."""
|
|
394
|
+
if not automation_id or not task_id:
|
|
395
|
+
return
|
|
396
|
+
with _LOCK:
|
|
397
|
+
current = _TASKS.get(automation_id)
|
|
398
|
+
if current is None or current.get("task_id") == task_id:
|
|
399
|
+
return
|
|
400
|
+
current["task_id"] = task_id
|
|
401
|
+
_save_locked()
|
|
402
|
+
|
|
336
403
|
def _launch_run(t):
|
|
337
404
|
"""拉起一次真实编排运行:与 main.py 的 /api/tasks 走同一条链路
|
|
338
405
|
(store.create_task → store.create_run → jobs 直接启动),返回 run_id。
|
|
@@ -353,12 +420,27 @@ def _launch_run(t):
|
|
|
353
420
|
task = None
|
|
354
421
|
run = None
|
|
355
422
|
try:
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
423
|
+
with _LAUNCH_LOCK:
|
|
424
|
+
persistent_id = str(t.get("task_id") or "").strip()
|
|
425
|
+
if persistent_id:
|
|
426
|
+
task = store.get_task(persistent_id)
|
|
427
|
+
if task is None or not _persistent_task_compatible(task, payload):
|
|
428
|
+
# The user may have deleted the backing task. Recreate the
|
|
429
|
+
# persistent conversation when its definition changed.
|
|
430
|
+
task = None
|
|
431
|
+
elif _active_run(persistent_id):
|
|
432
|
+
raise _AutomationBusy("上一次定时运行仍在执行,跳过本次重叠触发")
|
|
433
|
+
if task is None:
|
|
434
|
+
task = store.create_task(payload)
|
|
435
|
+
t["task_id"] = task["id"]
|
|
436
|
+
_bind_task_id(t.get("id"), task["id"])
|
|
437
|
+
run = store.create_run("orchestration", task["title"], task_id=task["id"])
|
|
438
|
+
store.update_task_status(task["id"], "queued")
|
|
439
|
+
jobs.enqueue({"kind": "orchestration", "run_id": run["id"],
|
|
440
|
+
"task_id": task["id"]})
|
|
441
|
+
return run["id"]
|
|
442
|
+
except _AutomationBusy:
|
|
443
|
+
raise
|
|
362
444
|
except Exception:
|
|
363
445
|
# 启动失败必须收口,不能留下看似仍在启动的任务/run。
|
|
364
446
|
# Keep the public error generic; the detailed traceback stays in the
|
|
@@ -389,6 +471,9 @@ def _fire(snapshot, now):
|
|
|
389
471
|
run_id = ""
|
|
390
472
|
try:
|
|
391
473
|
run_id = _launch_run(snapshot) or ""
|
|
474
|
+
except _AutomationBusy as e:
|
|
475
|
+
log.info("automation: 跳过重叠触发 %s: %s", snapshot.get("id"), e)
|
|
476
|
+
status = "busy"
|
|
392
477
|
except Exception as e:
|
|
393
478
|
log.warning("automation: 定时触发 %s 失败: %r", snapshot.get("id"), e)
|
|
394
479
|
status = "error"
|
|
@@ -515,6 +600,13 @@ def _tick():
|
|
|
515
600
|
(",%d 项失败" % len(r.get("errors") or [])) if r.get("errors") else "")
|
|
516
601
|
except Exception:
|
|
517
602
|
log.debug("automation: 每日清理跳过", exc_info=True)
|
|
603
|
+
# 模块外 tick 钩子(如评测台定时回归):钩子自节流、自行吞错
|
|
604
|
+
for _hook in list(_TICK_HOOKS):
|
|
605
|
+
try:
|
|
606
|
+
_hook()
|
|
607
|
+
except Exception:
|
|
608
|
+
log.debug("automation: tick 钩子跳过", exc_info=True)
|
|
609
|
+
|
|
518
610
|
|
|
519
611
|
|
|
520
612
|
def _loop():
|
|
@@ -637,6 +729,9 @@ def run_now(tid):
|
|
|
637
729
|
with _LOCK:
|
|
638
730
|
snapshot = dict(_TASKS[tid])
|
|
639
731
|
run_id = _launch_run(snapshot) or ""
|
|
732
|
+
except _AutomationBusy as e:
|
|
733
|
+
log.info("automation: 手动触发 %s 跳过重叠运行: %s", tid, e)
|
|
734
|
+
status = "busy"
|
|
640
735
|
except Exception as e:
|
|
641
736
|
log.warning("automation: 手动触发 %s 失败: %r", tid, e)
|
|
642
737
|
status = "error"
|