codebee 0.1.103 → 0.1.105

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