codebee 0.1.62 → 0.1.63

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 (98) hide show
  1. package/CHANGELOG.md +499 -490
  2. package/LICENSE +21 -21
  3. package/README.md +484 -486
  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/aiflavor.py +197 -197
  12. package/app/core/attachments.py +532 -532
  13. package/app/core/automation.py +758 -758
  14. package/app/core/backup.py +470 -470
  15. package/app/core/beekeeper.py +183 -183
  16. package/app/core/board.py +209 -209
  17. package/app/core/bookmeta_catalog.py +180 -180
  18. package/app/core/branching.py +137 -137
  19. package/app/core/builtin_agent.py +1191 -1191
  20. package/app/core/capability.py +144 -144
  21. package/app/core/catalog.py +19 -14
  22. package/app/core/chaptersafe.py +32 -32
  23. package/app/core/cleanup.py +303 -303
  24. package/app/core/compaction.py +186 -186
  25. package/app/core/covergen.py +356 -356
  26. package/app/core/diagnostics.py +114 -114
  27. package/app/core/dispatch.py +173 -173
  28. package/app/core/dispatch_log.py +113 -113
  29. package/app/core/env_scrub.py +85 -85
  30. package/app/core/error_codes.py +64 -64
  31. package/app/core/errorlog.py +137 -137
  32. package/app/core/flows.py +355 -355
  33. package/app/core/goal_service.py +167 -167
  34. package/app/core/health.py +341 -341
  35. package/app/core/history.py +38 -38
  36. package/app/core/hooks.py +156 -156
  37. package/app/core/jobs.py +1022 -1022
  38. package/app/core/manager.py +1996 -1996
  39. package/app/core/market.py +362 -362
  40. package/app/core/market_remote.py +66 -66
  41. package/app/core/mocks.py +64 -64
  42. package/app/core/modelhub.py +3266 -3266
  43. package/app/core/notify.py +106 -106
  44. package/app/core/paihang.py +109 -109
  45. package/app/core/paths.py +2 -2
  46. package/app/core/pipeline.py +4444 -4426
  47. package/app/core/planner.py +27 -27
  48. package/app/core/portguard.py +111 -111
  49. package/app/core/preview.py +293 -293
  50. package/app/core/publish/__init__.py +7 -7
  51. package/app/core/publish/auto.py +367 -367
  52. package/app/core/publish/browser.py +533 -533
  53. package/app/core/publish/fanqie.py +128 -128
  54. package/app/core/publish/flow.py +406 -406
  55. package/app/core/publish/ledger.py +198 -198
  56. package/app/core/publish/manager.py +590 -590
  57. package/app/core/publish/qimao.py +141 -141
  58. package/app/core/publish/ws.py +139 -139
  59. package/app/core/redact.py +38 -38
  60. package/app/core/repeat_guard.py +124 -124
  61. package/app/core/router.py +20 -3
  62. package/app/core/runner.py +2014 -1991
  63. package/app/core/selfupdate.py +384 -384
  64. package/app/core/session_log.py +161 -161
  65. package/app/core/sessions.py +312 -312
  66. package/app/core/settings.py +149 -149
  67. package/app/core/settings_schema.py +262 -262
  68. package/app/core/share_page.py +114 -114
  69. package/app/core/skill_scan.py +106 -106
  70. package/app/core/skillpacks/fanqie-novel.md +80 -80
  71. package/app/core/skillpacks/market/character-bible.md +66 -66
  72. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  73. package/app/core/skillpacks/market/git-workflow.md +57 -57
  74. package/app/core/skillpacks/market/release-notes.md +72 -72
  75. package/app/core/skillpacks/market/weekly-report.md +71 -71
  76. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  77. package/app/core/step_runner.py +132 -132
  78. package/app/core/task_compile.py +199 -199
  79. package/app/core/telemetry.py +292 -292
  80. package/app/core/tlsctx.py +64 -64
  81. package/app/core/token_meter.py +147 -147
  82. package/app/core/volumes.py +355 -355
  83. package/app/core/wxdigest.py +858 -858
  84. package/app/core/zentao.py +2090 -2090
  85. package/app/main.py +2826 -2826
  86. package/app/pick_dialog.py +110 -110
  87. package/app/ui/app.js +13977 -13977
  88. package/app/ui/board.html +162 -162
  89. package/app/ui/board.js +305 -305
  90. package/app/ui/i18n.js +2711 -2711
  91. package/app/ui/icons/bee.svg +79 -79
  92. package/app/ui/index.html +1 -1
  93. package/app/ui/manifest.json +16 -16
  94. package/app/ui/qrcode.js +2297 -2297
  95. package/app/ui/style.css +6125 -6125
  96. package/bin/tutti.js +147 -147
  97. package/package.json +41 -41
  98. package/tools/wx_reader.py +261 -261
@@ -1,168 +1,168 @@
1
- # -*- coding: utf-8 -*-
2
- """Goal 状态外置:跨轮目标(title/描述/phase/轮次)独立于 LLM 调用持久化。
3
-
4
- 设计稿:docs/migration/03-state-externalization.md §2A。
5
- 参考 dsh packages/goal/goal/:状态 100% 来自事件溯源(goal/change 事件),
6
- 唯一当前 goal,revision 走 CAS 拒绝陈旧引用。
7
-
8
- 与 planner.py 的关系:planner 继续负责「LLM 拆解」;GoalService 只管
9
- 「目标状态」——谁都可以读(UI/续行驱动器/流水线),变更必须过 CAS。
10
- """
11
- from __future__ import annotations
12
-
13
- import json
14
- import threading
15
- import time
16
- import uuid
17
- from pathlib import Path
18
-
19
- _LOCK = threading.RLock()
20
- # goal_id -> dict(内存缓存;磁盘为唯一事实来源)
21
- _GOALS = {}
22
- _FILE = None # 由 init() 注入
23
-
24
-
25
- def init(data_dir=None):
26
- """注入存储路径并从磁盘加载。测试可传临时目录。"""
27
- global _FILE, _GOALS
28
- with _LOCK:
29
- base = Path(data_dir) if data_dir else Path(_default_dir())
30
- base.mkdir(parents=True, exist_ok=True)
31
- _FILE = base / "goals.json"
32
- _GOALS = {}
33
- try:
34
- data = json.loads(_FILE.read_text(encoding="utf-8"))
35
- for g in data.get("goals") or []:
36
- if isinstance(g, dict) and g.get("goal_id"):
37
- _GOALS[g["goal_id"]] = g
38
- except (OSError, json.JSONDecodeError):
39
- pass
40
-
41
-
42
- def _default_dir():
43
- from . import paths
44
- return str(paths.DATA_DIR)
45
-
46
-
47
- def _persist():
48
- """原子写盘(tmp + replace)。调用方必须已持有 _LOCK。"""
49
- if _FILE is None:
50
- return
51
- tmp = _FILE.with_suffix(".tmp")
52
- tmp.write_text(json.dumps({"goals": list(_GOALS.values())},
53
- ensure_ascii=False, indent=2), encoding="utf-8")
54
- tmp.replace(_FILE)
55
-
56
-
57
- class StaleRevisionError(Exception):
58
- """CAS 失败:调用方持有的是陈旧 revision。"""
59
-
60
-
61
- class GoalExistsError(Exception):
62
- """已有未完结的当前 goal,需先 complete/abandon。"""
63
-
64
-
65
- class GoalNotFoundError(Exception):
66
- pass
67
-
68
-
69
- def _valid_phase(p):
70
- return p in ("active", "paused", "complete", "abandoned")
71
-
72
-
73
- def current():
74
- """返回唯一「当前」goal(未 complete/abandoned 的最新一个);无则 None。"""
75
- with _LOCK:
76
- open_goals = [g for g in _GOALS.values()
77
- if g.get("phase") in ("active", "paused")]
78
- if not open_goals:
79
- return None
80
- return max(open_goals, key=lambda g: g.get("created_at", 0))
81
-
82
-
83
- def get(goal_id: str):
84
- with _LOCK:
85
- g = _GOALS.get(goal_id)
86
- return dict(g) if g else None
87
-
88
-
89
- def create(title: str, description: str = "", *, rounds_max: int | None = None,
90
- metadata: dict | None = None) -> dict:
91
- """创建新 goal。已有未完结 goal 时拒绝(单一当前目标,仿 dsh)。
92
-
93
- rounds_max 缺省读 settings_v2 orchestrator.max_goal_rounds(读不到回落 5)。"""
94
- if rounds_max is None:
95
- try:
96
- from .settings_schema import get as ss_get, register_default_namespaces
97
- register_default_namespaces()
98
- rounds_max = int(ss_get("orchestrator", "max_goal_rounds") or 5)
99
- except Exception:
100
- rounds_max = 5
101
- with _LOCK:
102
- cur = current()
103
- if cur is not None:
104
- raise GoalExistsError(
105
- "已有进行中的 goal %s(%s),请先 complete/abandon"
106
- % (cur["goal_id"], cur["title"]))
107
- goal = {
108
- "goal_id": "g" + uuid.uuid4().hex[:12],
109
- "title": (title or "").strip(),
110
- "description": (description or "").strip(),
111
- "phase": "active",
112
- "revision": 1,
113
- "rounds_started": 0,
114
- "rounds_max": max(1, int(rounds_max)),
115
- "created_at": time.strftime("%Y-%m-%d %H:%M:%S"),
116
- "updated_at": time.strftime("%Y-%m-%d %H:%M:%S"),
117
- "metadata": dict(metadata or {}),
118
- }
119
- _GOALS[goal["goal_id"]] = goal
120
- _persist()
121
- return dict(goal)
122
-
123
-
124
- def update(goal_id: str, expected_revision: int, **changes) -> dict:
125
- """CAS 更新:expected_revision 不匹配即抛 StaleRevisionError。
126
-
127
- 允许变更的字段:title/description/phase/rounds_max/metadata。
128
- phase 只允许合法值;complete/abandoned 为终态不可再改回。
129
- """
130
- with _LOCK:
131
- g = _GOALS.get(goal_id)
132
- if not g:
133
- raise GoalNotFoundError(goal_id)
134
- if g["revision"] != expected_revision:
135
- raise StaleRevisionError(
136
- "%s 期望 rev %d,实际 %d" % (goal_id, expected_revision, g["revision"]))
137
- if g["phase"] in ("complete", "abandoned"):
138
- raise ValueError("goal 已终态(%s),不可再变更" % g["phase"])
139
- allowed = {"title", "description", "phase", "rounds_max",
140
- "rounds_started", "metadata"}
141
- for k, v in changes.items():
142
- if k not in allowed:
143
- raise ValueError("不可变更字段:%s" % k)
144
- if k == "phase" and not _valid_phase(v):
145
- raise ValueError("非法 phase:%s" % v)
146
- if k == "rounds_max":
147
- v = max(1, int(v))
148
- g[k] = v
149
- g["revision"] += 1
150
- g["updated_at"] = time.strftime("%Y-%m-%d %H:%M:%S")
151
- _persist()
152
- return dict(g)
153
-
154
-
155
- def increment_rounds(goal_id: str, expected_revision: int) -> dict:
156
- """轮次 +1(Goal Round Driver 专用 CAS 入口)。"""
157
- with _LOCK:
158
- g = _GOALS.get(goal_id)
159
- if not g:
160
- raise GoalNotFoundError(goal_id)
161
- return update(goal_id, expected_revision, rounds_started=g["rounds_started"] + 1)
162
-
163
-
164
- def list_goals(limit: int = 50) -> list:
165
- with _LOCK:
166
- goals = sorted(_GOALS.values(),
167
- key=lambda g: g.get("created_at", ""), reverse=True)
1
+ # -*- coding: utf-8 -*-
2
+ """Goal 状态外置:跨轮目标(title/描述/phase/轮次)独立于 LLM 调用持久化。
3
+
4
+ 设计稿:docs/migration/03-state-externalization.md §2A。
5
+ 参考 dsh packages/goal/goal/:状态 100% 来自事件溯源(goal/change 事件),
6
+ 唯一当前 goal,revision 走 CAS 拒绝陈旧引用。
7
+
8
+ 与 planner.py 的关系:planner 继续负责「LLM 拆解」;GoalService 只管
9
+ 「目标状态」——谁都可以读(UI/续行驱动器/流水线),变更必须过 CAS。
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import threading
15
+ import time
16
+ import uuid
17
+ from pathlib import Path
18
+
19
+ _LOCK = threading.RLock()
20
+ # goal_id -> dict(内存缓存;磁盘为唯一事实来源)
21
+ _GOALS = {}
22
+ _FILE = None # 由 init() 注入
23
+
24
+
25
+ def init(data_dir=None):
26
+ """注入存储路径并从磁盘加载。测试可传临时目录。"""
27
+ global _FILE, _GOALS
28
+ with _LOCK:
29
+ base = Path(data_dir) if data_dir else Path(_default_dir())
30
+ base.mkdir(parents=True, exist_ok=True)
31
+ _FILE = base / "goals.json"
32
+ _GOALS = {}
33
+ try:
34
+ data = json.loads(_FILE.read_text(encoding="utf-8"))
35
+ for g in data.get("goals") or []:
36
+ if isinstance(g, dict) and g.get("goal_id"):
37
+ _GOALS[g["goal_id"]] = g
38
+ except (OSError, json.JSONDecodeError):
39
+ pass
40
+
41
+
42
+ def _default_dir():
43
+ from . import paths
44
+ return str(paths.DATA_DIR)
45
+
46
+
47
+ def _persist():
48
+ """原子写盘(tmp + replace)。调用方必须已持有 _LOCK。"""
49
+ if _FILE is None:
50
+ return
51
+ tmp = _FILE.with_suffix(".tmp")
52
+ tmp.write_text(json.dumps({"goals": list(_GOALS.values())},
53
+ ensure_ascii=False, indent=2), encoding="utf-8")
54
+ tmp.replace(_FILE)
55
+
56
+
57
+ class StaleRevisionError(Exception):
58
+ """CAS 失败:调用方持有的是陈旧 revision。"""
59
+
60
+
61
+ class GoalExistsError(Exception):
62
+ """已有未完结的当前 goal,需先 complete/abandon。"""
63
+
64
+
65
+ class GoalNotFoundError(Exception):
66
+ pass
67
+
68
+
69
+ def _valid_phase(p):
70
+ return p in ("active", "paused", "complete", "abandoned")
71
+
72
+
73
+ def current():
74
+ """返回唯一「当前」goal(未 complete/abandoned 的最新一个);无则 None。"""
75
+ with _LOCK:
76
+ open_goals = [g for g in _GOALS.values()
77
+ if g.get("phase") in ("active", "paused")]
78
+ if not open_goals:
79
+ return None
80
+ return max(open_goals, key=lambda g: g.get("created_at", 0))
81
+
82
+
83
+ def get(goal_id: str):
84
+ with _LOCK:
85
+ g = _GOALS.get(goal_id)
86
+ return dict(g) if g else None
87
+
88
+
89
+ def create(title: str, description: str = "", *, rounds_max: int | None = None,
90
+ metadata: dict | None = None) -> dict:
91
+ """创建新 goal。已有未完结 goal 时拒绝(单一当前目标,仿 dsh)。
92
+
93
+ rounds_max 缺省读 settings_v2 orchestrator.max_goal_rounds(读不到回落 5)。"""
94
+ if rounds_max is None:
95
+ try:
96
+ from .settings_schema import get as ss_get, register_default_namespaces
97
+ register_default_namespaces()
98
+ rounds_max = int(ss_get("orchestrator", "max_goal_rounds") or 5)
99
+ except Exception:
100
+ rounds_max = 5
101
+ with _LOCK:
102
+ cur = current()
103
+ if cur is not None:
104
+ raise GoalExistsError(
105
+ "已有进行中的 goal %s(%s),请先 complete/abandon"
106
+ % (cur["goal_id"], cur["title"]))
107
+ goal = {
108
+ "goal_id": "g" + uuid.uuid4().hex[:12],
109
+ "title": (title or "").strip(),
110
+ "description": (description or "").strip(),
111
+ "phase": "active",
112
+ "revision": 1,
113
+ "rounds_started": 0,
114
+ "rounds_max": max(1, int(rounds_max)),
115
+ "created_at": time.strftime("%Y-%m-%d %H:%M:%S"),
116
+ "updated_at": time.strftime("%Y-%m-%d %H:%M:%S"),
117
+ "metadata": dict(metadata or {}),
118
+ }
119
+ _GOALS[goal["goal_id"]] = goal
120
+ _persist()
121
+ return dict(goal)
122
+
123
+
124
+ def update(goal_id: str, expected_revision: int, **changes) -> dict:
125
+ """CAS 更新:expected_revision 不匹配即抛 StaleRevisionError。
126
+
127
+ 允许变更的字段:title/description/phase/rounds_max/metadata。
128
+ phase 只允许合法值;complete/abandoned 为终态不可再改回。
129
+ """
130
+ with _LOCK:
131
+ g = _GOALS.get(goal_id)
132
+ if not g:
133
+ raise GoalNotFoundError(goal_id)
134
+ if g["revision"] != expected_revision:
135
+ raise StaleRevisionError(
136
+ "%s 期望 rev %d,实际 %d" % (goal_id, expected_revision, g["revision"]))
137
+ if g["phase"] in ("complete", "abandoned"):
138
+ raise ValueError("goal 已终态(%s),不可再变更" % g["phase"])
139
+ allowed = {"title", "description", "phase", "rounds_max",
140
+ "rounds_started", "metadata"}
141
+ for k, v in changes.items():
142
+ if k not in allowed:
143
+ raise ValueError("不可变更字段:%s" % k)
144
+ if k == "phase" and not _valid_phase(v):
145
+ raise ValueError("非法 phase:%s" % v)
146
+ if k == "rounds_max":
147
+ v = max(1, int(v))
148
+ g[k] = v
149
+ g["revision"] += 1
150
+ g["updated_at"] = time.strftime("%Y-%m-%d %H:%M:%S")
151
+ _persist()
152
+ return dict(g)
153
+
154
+
155
+ def increment_rounds(goal_id: str, expected_revision: int) -> dict:
156
+ """轮次 +1(Goal Round Driver 专用 CAS 入口)。"""
157
+ with _LOCK:
158
+ g = _GOALS.get(goal_id)
159
+ if not g:
160
+ raise GoalNotFoundError(goal_id)
161
+ return update(goal_id, expected_revision, rounds_started=g["rounds_started"] + 1)
162
+
163
+
164
+ def list_goals(limit: int = 50) -> list:
165
+ with _LOCK:
166
+ goals = sorted(_GOALS.values(),
167
+ key=lambda g: g.get("created_at", ""), reverse=True)
168
168
  return [dict(g) for g in goals[:limit]]