codebee 0.1.101 → 0.1.103

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 (134) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +21 -21
  3. package/README.md +3 -6
  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/acp_client.py +457 -426
  13. package/app/core/aiflavor.py +197 -197
  14. package/app/core/analytics.py +38 -38
  15. package/app/core/artifacts.py +148 -148
  16. package/app/core/assets.py +110 -110
  17. package/app/core/attachments.py +623 -623
  18. package/app/core/audit.py +30 -30
  19. package/app/core/backend_profiles.py +188 -188
  20. package/app/core/backup.py +470 -470
  21. package/app/core/beekeeper.py +183 -183
  22. package/app/core/benchstore.py +102 -102
  23. package/app/core/board.py +21 -17
  24. package/app/core/bookmeta.py +1036 -1036
  25. package/app/core/bookmeta_catalog.py +180 -180
  26. package/app/core/branching.py +137 -137
  27. package/app/core/builtin_agent.py +13 -4
  28. package/app/core/capability.py +144 -144
  29. package/app/core/catalog.py +28 -6
  30. package/app/core/chaptersafe.py +32 -32
  31. package/app/core/checkpoints.py +117 -31
  32. package/app/core/cleanup.py +303 -303
  33. package/app/core/command_auth.py +302 -302
  34. package/app/core/compaction.py +201 -201
  35. package/app/core/contracts.py +255 -255
  36. package/app/core/covergen.py +356 -356
  37. package/app/core/defectretro.py +43 -43
  38. package/app/core/diagnostics.py +114 -114
  39. package/app/core/dispatch.py +1 -1
  40. package/app/core/dispatch_log.py +128 -128
  41. package/app/core/env_scrub.py +85 -85
  42. package/app/core/errorlog.py +137 -137
  43. package/app/core/eval_matrix.py +91 -82
  44. package/app/core/evaluation.py +174 -174
  45. package/app/core/failure_notify.py +290 -290
  46. package/app/core/flow_graph.py +33 -33
  47. package/app/core/goal_service.py +167 -167
  48. package/app/core/health.py +389 -389
  49. package/app/core/history.py +38 -38
  50. package/app/core/hooks.py +162 -162
  51. package/app/core/knowledge_pipeline.py +86 -86
  52. package/app/core/manager.py +2385 -2385
  53. package/app/core/mcp_client.py +7 -2
  54. package/app/core/mocks.py +64 -64
  55. package/app/core/notify.py +201 -201
  56. package/app/core/novel_quality.py +181 -181
  57. package/app/core/openapi.py +217 -209
  58. package/app/core/operations.py +228 -228
  59. package/app/core/paihang.py +145 -145
  60. package/app/core/pipeline.py +6 -3
  61. package/app/core/policy.py +9 -0
  62. package/app/core/portguard.py +111 -111
  63. package/app/core/presence.py +77 -77
  64. package/app/core/preview.py +293 -293
  65. package/app/core/project_memory.py +206 -206
  66. package/app/core/prompt_guard.py +78 -78
  67. package/app/core/publish/__init__.py +7 -7
  68. package/app/core/publish/auto.py +659 -562
  69. package/app/core/publish/chaprows.py +138 -138
  70. package/app/core/publish/fanqie.py +16 -10
  71. package/app/core/publish/flow.py +29 -1
  72. package/app/core/publish/flows-fanqie-calibrated.json +442 -442
  73. package/app/core/publish/manager.py +1185 -1177
  74. package/app/core/publish/qimao.py +193 -193
  75. package/app/core/publish/volumes.py +108 -108
  76. package/app/core/publish/ws.py +139 -139
  77. package/app/core/quality_gate.py +201 -201
  78. package/app/core/redact.py +38 -38
  79. package/app/core/repeat_guard.py +129 -129
  80. package/app/core/retrieval.py +137 -137
  81. package/app/core/sandbox_runtime.py +95 -0
  82. package/app/core/selfupdate.py +384 -384
  83. package/app/core/session_log.py +161 -161
  84. package/app/core/sessions.py +312 -312
  85. package/app/core/settings.py +215 -215
  86. package/app/core/settings_schema.py +282 -282
  87. package/app/core/share_page.py +114 -114
  88. package/app/core/skill_scan.py +139 -139
  89. package/app/core/skillpacks/market/character-bible.md +66 -66
  90. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  91. package/app/core/skillpacks/market/git-workflow.md +57 -57
  92. package/app/core/skillpacks/market/release-notes.md +72 -72
  93. package/app/core/skillpacks/market/weekly-report.md +71 -71
  94. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  95. package/app/core/step_runner.py +132 -132
  96. package/app/core/store.py +13 -3
  97. package/app/core/story_tracking.py +275 -275
  98. package/app/core/task_compile.py +199 -199
  99. package/app/core/telemetry.py +292 -292
  100. package/app/core/tlsctx.py +64 -64
  101. package/app/core/token_meter.py +147 -147
  102. package/app/core/tool_outputs.py +107 -107
  103. package/app/core/tracing.py +207 -207
  104. package/app/core/upstream.py +41 -41
  105. package/app/core/usage.py +1038 -1038
  106. package/app/core/volumes.py +389 -389
  107. package/app/core/webhooks.py +89 -89
  108. package/app/core/wxdigest.py +865 -865
  109. package/app/core/zentao.py +2692 -2692
  110. package/app/main.py +1 -0
  111. package/app/mcp_server.py +244 -244
  112. package/app/output/_createbook_body.json +1 -0
  113. package/app/output/_probe_fq_click.py +50 -0
  114. package/app/output/_probe_fq_form.py +42 -0
  115. package/app/output/_probe_fq_modal.py +55 -0
  116. package/app/output/_probe_fq_points.py +71 -0
  117. package/app/output/_probe_fq_submit.py +34 -0
  118. package/app/output/_probe_fq_watch.py +62 -0
  119. package/app/output/workbench-clean-preview.jpg +0 -0
  120. package/app/output/workbench-reference-render.png +0 -0
  121. package/app/output/workbench-shell.png +0 -0
  122. package/app/pick_dialog.py +110 -110
  123. package/app/ui/app.js +165 -29
  124. package/app/ui/artdiff.js +74 -74
  125. package/app/ui/hive3d.js +491 -223
  126. package/app/ui/i18n.js +18 -1
  127. package/app/ui/icons/bee.svg +79 -79
  128. package/app/ui/index.html +24 -4
  129. package/app/ui/manifest.json +16 -16
  130. package/app/ui/qrcode.js +2297 -2297
  131. package/app/ui/style.css +548 -18
  132. package/bin/tutti.js +147 -147
  133. package/package.json +1 -1
  134. 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]]