codebee 0.1.95 → 0.1.97
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 +733 -720
- package/LICENSE +21 -21
- package/README.md +516 -509
- package/app/core/acceptance.py +100 -100
- package/app/core/agent_context.py +118 -0
- package/app/core/aiflavor.py +197 -197
- package/app/core/analytics.py +38 -38
- 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 -30
- package/app/core/backup.py +470 -470
- package/app/core/beekeeper.py +183 -183
- package/app/core/benchstore.py +102 -95
- package/app/core/board.py +246 -209
- package/app/core/bookmeta.py +1036 -1036
- package/app/core/bookmeta_catalog.py +180 -180
- package/app/core/branching.py +137 -137
- package/app/core/builtin_agent.py +2055 -1680
- package/app/core/capability.py +144 -144
- package/app/core/chaptersafe.py +32 -32
- package/app/core/checkpoints.py +648 -0
- package/app/core/cleanup.py +303 -303
- package/app/core/compaction.py +201 -201
- package/app/core/contracts.py +255 -255
- 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 +198 -191
- package/app/core/dispatch_log.py +128 -128
- package/app/core/env_scrub.py +85 -85
- package/app/core/errorlog.py +137 -137
- package/app/core/eval_matrix.py +82 -0
- package/app/core/evalbench.py +184 -91
- package/app/core/evaluation.py +174 -174
- package/app/core/failure_notify.py +290 -290
- package/app/core/flow_graph.py +33 -0
- package/app/core/goal_service.py +167 -167
- package/app/core/health.py +389 -389
- package/app/core/history.py +38 -38
- package/app/core/hooks.py +162 -162
- package/app/core/jobs.py +25 -4
- package/app/core/knowledge_pipeline.py +86 -0
- package/app/core/manager.py +2232 -2232
- package/app/core/mcp_client.py +200 -29
- package/app/core/mocks.py +64 -64
- package/app/core/notify.py +201 -201
- package/app/core/novel_quality.py +181 -189
- package/app/core/openapi.py +177 -154
- package/app/core/operations.py +228 -228
- package/app/core/paihang.py +145 -145
- package/app/core/pipeline.py +319 -59
- package/app/core/planner.py +17 -11
- package/app/core/policy.py +84 -0
- package/app/core/portguard.py +111 -111
- package/app/core/presence.py +77 -77
- package/app/core/preview.py +293 -293
- package/app/core/project_memory.py +139 -0
- package/app/core/prompt_guard.py +78 -78
- package/app/core/publish/__init__.py +7 -7
- package/app/core/publish/auto.py +106 -22
- package/app/core/publish/browser.py +694 -655
- package/app/core/publish/chaprows.py +138 -0
- package/app/core/publish/fanqie.py +212 -165
- package/app/core/publish/flow.py +832 -640
- package/app/core/publish/flows-fanqie-calibrated.json +361 -336
- package/app/core/publish/ledger.py +51 -2
- package/app/core/publish/manager.py +1150 -1118
- package/app/core/publish/qimao.py +176 -176
- package/app/core/publish/volumes.py +108 -0
- package/app/core/publish/ws.py +139 -139
- package/app/core/quality_gate.py +194 -194
- package/app/core/redact.py +38 -38
- package/app/core/repeat_guard.py +129 -129
- package/app/core/retrieval.py +137 -137
- package/app/core/router.py +398 -398
- package/app/core/runner.py +2239 -2238
- 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 +33 -5
- package/app/core/settings_schema.py +282 -271
- package/app/core/share_page.py +114 -114
- package/app/core/skill_scan.py +139 -139
- package/app/core/skillpacks/fanqie-novel.md +8 -6
- 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/skillpacks/qimao-signing.md +14 -11
- package/app/core/skills.py +32 -21
- package/app/core/step_runner.py +132 -132
- package/app/core/store.py +24 -0
- package/app/core/story_tracking.py +275 -275
- 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/tool_outputs.py +107 -0
- package/app/core/tracing.py +207 -144
- package/app/core/upstream.py +41 -41
- package/app/core/usage.py +1038 -1038
- package/app/core/volumes.py +389 -389
- package/app/core/webhooks.py +89 -0
- package/app/core/wxdigest.py +865 -858
- package/app/core/zentao.py +2692 -2692
- package/app/main.py +179 -10
- package/app/mcp_server.py +244 -244
- package/app/pick_dialog.py +110 -110
- package/app/ui/app.js +563 -21
- package/app/ui/artdiff.js +74 -74
- package/app/ui/assets/community-qr.png +0 -0
- package/app/ui/assets/donate-alipay.jpg +0 -0
- package/app/ui/assets/donate-qr.jpg +0 -0
- package/app/ui/assets/workbench-reference.png +0 -0
- package/app/ui/board.css +451 -666
- package/app/ui/board.html +165 -162
- package/app/ui/board.js +509 -305
- package/app/ui/hive3d.js +242 -0
- package/app/ui/i18n.js +125 -9
- package/app/ui/icons/bee.svg +79 -79
- package/app/ui/index.html +60 -3
- package/app/ui/manifest.json +16 -16
- package/app/ui/qrcode.js +2297 -2297
- package/app/ui/style.css +189 -9
- package/bin/tutti.js +147 -147
- package/package.json +49 -48
- package/tools/wx_reader.py +261 -261
- package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +0 -88
- package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +0 -126
- 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 +0 -210
- 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/output/_createbook_body.json +0 -1
- package/app/output/_probe_fq_click.py +0 -50
- package/app/output/_probe_fq_form.py +0 -42
- package/app/output/_probe_fq_modal.py +0 -55
- package/app/output/_probe_fq_points.py +0 -71
- package/app/output/_probe_fq_submit.py +0 -34
- package/app/output/_probe_fq_watch.py +0 -62
package/app/main.py
CHANGED
|
@@ -143,6 +143,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
143
143
|
return cached
|
|
144
144
|
raw_len = self.headers.get("Content-Length")
|
|
145
145
|
if not raw_len:
|
|
146
|
+
self._raw_body = b""
|
|
146
147
|
body = {}
|
|
147
148
|
else:
|
|
148
149
|
try:
|
|
@@ -158,6 +159,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
158
159
|
body = {}
|
|
159
160
|
else:
|
|
160
161
|
data = self.rfile.read(n)
|
|
162
|
+
self._raw_body = data
|
|
161
163
|
try:
|
|
162
164
|
text = data.decode("utf-8")
|
|
163
165
|
except UnicodeDecodeError:
|
|
@@ -171,6 +173,12 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
171
173
|
self._parsed_body = body
|
|
172
174
|
return body
|
|
173
175
|
|
|
176
|
+
def _raw_request_body(self):
|
|
177
|
+
"""Return the exact bytes used for webhook signature verification."""
|
|
178
|
+
if not hasattr(self, "_raw_body"):
|
|
179
|
+
self._body()
|
|
180
|
+
return getattr(self, "_raw_body", b"")
|
|
181
|
+
|
|
174
182
|
# ------------------------------------------------------------ 远程访问
|
|
175
183
|
def _forwarded_ip(self):
|
|
176
184
|
"""经代理进来的真实客户端 IP:X-Forwarded-For 首跳,缺省 CF-Connecting-IP。"""
|
|
@@ -278,6 +286,13 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
278
286
|
if path == "/api/board":
|
|
279
287
|
# 任务驾驶舱大屏的轻量聚合(KB 级;别让它去拉 /api/state 全量)
|
|
280
288
|
return self._json(200, board.payload())
|
|
289
|
+
if path == "/api/board/run":
|
|
290
|
+
# 大屏下钻抽屉的步骤链:点开才拉,不进 /api/board 的 8 秒缓存
|
|
291
|
+
rid = (parse_qs(urlparse(self.path).query).get("run_id") or [""])[0]
|
|
292
|
+
detail = board.run_detail(rid)
|
|
293
|
+
if not detail:
|
|
294
|
+
return self._json(404, {"error": "not found"})
|
|
295
|
+
return self._json(200, detail)
|
|
281
296
|
if path == "/api/analytics":
|
|
282
297
|
from core import analytics
|
|
283
298
|
return self._json(200, analytics.summary(store))
|
|
@@ -300,6 +315,13 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
300
315
|
qs = parse_qs(urlparse(self.path).query)
|
|
301
316
|
fid = (qs.get("id") or [""])[0]
|
|
302
317
|
return self._json(200, {"versions": flows.flow_versions(fid)})
|
|
318
|
+
m = re.match(r"^/api/flows/([^/]+)/graph$", path)
|
|
319
|
+
if m:
|
|
320
|
+
from core import flow_graph
|
|
321
|
+
try:
|
|
322
|
+
return self._json(200, {"graph": flow_graph.build(m.group(1))})
|
|
323
|
+
except ValueError as exc:
|
|
324
|
+
return self._json(404, {"error": str(exc)})
|
|
303
325
|
if path == "/api/skills":
|
|
304
326
|
from core import skills
|
|
305
327
|
v = skills.view()
|
|
@@ -326,6 +348,16 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
326
348
|
if path == "/api/knowledge":
|
|
327
349
|
from core import knowledge
|
|
328
350
|
return self._json(200, knowledge.view())
|
|
351
|
+
m = re.match(r"^/api/tasks/([^/]+)/memory$", path)
|
|
352
|
+
if m:
|
|
353
|
+
task = store.get_task(m.group(1))
|
|
354
|
+
if not task:
|
|
355
|
+
return self._json(404, {"error": "not found"})
|
|
356
|
+
from core import project_memory
|
|
357
|
+
try:
|
|
358
|
+
return self._json(200, {"entries": project_memory.list_entries(task.get("workdir"))})
|
|
359
|
+
except (OSError, ValueError) as exc:
|
|
360
|
+
return self._json(409, {"error": str(exc)})
|
|
329
361
|
if path == "/api/agents/presence":
|
|
330
362
|
from core import presence
|
|
331
363
|
return self._json(200, {"agents": presence.list_agents()})
|
|
@@ -339,8 +371,18 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
339
371
|
return self._json(400, {"error": str(exc)})
|
|
340
372
|
return self._json(200, {"query": (q.get("q") or [""])[0][:500],
|
|
341
373
|
"results": results})
|
|
374
|
+
if path == "/api/knowledge/pipeline":
|
|
375
|
+
from core import knowledge_pipeline
|
|
376
|
+
q = parse_qs(urlparse(self.path).query)
|
|
377
|
+
return self._json(200, knowledge_pipeline.quality_report(
|
|
378
|
+
(q.get("ingest_id") or [""])[0]))
|
|
379
|
+
if path == "/api/policy":
|
|
380
|
+
from core import policy
|
|
381
|
+
q = parse_qs(urlparse(self.path).query)
|
|
382
|
+
return self._json(200, policy.normalize_sandbox(
|
|
383
|
+
{}, (q.get("workdir") or [settings.default_workdir()])[0]))
|
|
342
384
|
if path == "/api/settings":
|
|
343
|
-
return self._json(200, dict(settings.
|
|
385
|
+
return self._json(200, dict(settings.public_view(), **jobs.workers_info()))
|
|
344
386
|
if path == "/api/settings-v2":
|
|
345
387
|
# schema 化设置全貌(secret 已脱敏;前端调参卡直读)
|
|
346
388
|
from core import settings_schema as ss2
|
|
@@ -554,6 +596,24 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
554
596
|
from core import dispatch_log, tracing
|
|
555
597
|
return self._json(200, {"trace": tracing.build_trace(
|
|
556
598
|
run, dispatch_log.replay(run_id=m.group(1), limit=200))})
|
|
599
|
+
m = re.match(r"^/api/runs/([^/]+)/otel$", path)
|
|
600
|
+
if m:
|
|
601
|
+
run = store.get_run(m.group(1))
|
|
602
|
+
if not run:
|
|
603
|
+
return self._json(404, {"error": "not found"})
|
|
604
|
+
from core import dispatch_log, tracing
|
|
605
|
+
trace = tracing.build_trace(run, dispatch_log.replay(run_id=m.group(1), limit=200))
|
|
606
|
+
return self._json(200, tracing.to_otel(trace))
|
|
607
|
+
m = re.match(r"^/api/runs/([^/]+)/checkpoints$", path)
|
|
608
|
+
if m:
|
|
609
|
+
if not store.get_run(m.group(1)):
|
|
610
|
+
return self._json(404, {"error": "not found"})
|
|
611
|
+
from core import checkpoints
|
|
612
|
+
run = store.get_run(m.group(1)) or {}
|
|
613
|
+
task = store.get_task(run.get("task_id")) or {}
|
|
614
|
+
return self._json(200, {
|
|
615
|
+
"checkpoint": checkpoints.replay_preview(m.group(1)),
|
|
616
|
+
"files": checkpoints.snapshot_preview(m.group(1), task.get("workdir") or "")})
|
|
557
617
|
m = re.match(r"^/api/runs/([^/]+)/preview$", path)
|
|
558
618
|
if m:
|
|
559
619
|
# 网页成品预览:入口 HTML + 同目录代码文件(前端渲染「预览」页签)
|
|
@@ -847,6 +907,22 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
847
907
|
return self._json(200, {"ok": ok, "output": out})
|
|
848
908
|
if path == "/api/tasks/clarify":
|
|
849
909
|
return self._api_task_clarify()
|
|
910
|
+
m = re.match(r"^/api/tasks/([^/]+)/memory$", path)
|
|
911
|
+
if m:
|
|
912
|
+
task = store.get_task(m.group(1))
|
|
913
|
+
if not task:
|
|
914
|
+
return self._json(404, {"error": "not found"})
|
|
915
|
+
body = self._body() or {}
|
|
916
|
+
from core import project_memory
|
|
917
|
+
try:
|
|
918
|
+
row = project_memory.decide(task.get("workdir"), body.get("id"),
|
|
919
|
+
body.get("action"),
|
|
920
|
+
expected_version=body.get("expected_version"))
|
|
921
|
+
return self._json(200, {"entry": row})
|
|
922
|
+
except KeyError as exc:
|
|
923
|
+
return self._json(404, {"error": str(exc)})
|
|
924
|
+
except (OSError, TypeError, ValueError) as exc:
|
|
925
|
+
return self._json(400, {"error": str(exc)})
|
|
850
926
|
if path == "/api/agents/presence":
|
|
851
927
|
from core import presence
|
|
852
928
|
body = self._body() or {}
|
|
@@ -947,7 +1023,8 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
947
1023
|
return self._json(200, dict(ok=True, **info))
|
|
948
1024
|
if path == "/api/evalbench/cancel":
|
|
949
1025
|
from core import evalbench
|
|
950
|
-
|
|
1026
|
+
body = self._body() or {}
|
|
1027
|
+
return self._json(200, evalbench.cancel(force=bool(body.get("force"))))
|
|
951
1028
|
if path == "/api/evalbench/sample-op":
|
|
952
1029
|
# 自定义样题增删:{op:add|delete, sample:{...}|sid}
|
|
953
1030
|
from core import evalbench
|
|
@@ -972,6 +1049,32 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
972
1049
|
if err:
|
|
973
1050
|
return self._json(400, {"error": err})
|
|
974
1051
|
return self._json(200, dict(ok=True, **res))
|
|
1052
|
+
if path == "/api/eval-matrix/normalize":
|
|
1053
|
+
from core import eval_matrix
|
|
1054
|
+
try:
|
|
1055
|
+
return self._json(200, {"manifest": eval_matrix.normalize_manifest(self._body())})
|
|
1056
|
+
except ValueError as exc:
|
|
1057
|
+
return self._json(400, {"error": str(exc)})
|
|
1058
|
+
if path == "/api/eval-matrix/evaluate":
|
|
1059
|
+
from core import eval_matrix
|
|
1060
|
+
body = self._body() or {}
|
|
1061
|
+
try:
|
|
1062
|
+
report = eval_matrix.evaluate(body.get("manifest"),
|
|
1063
|
+
body.get("results"), body.get("baseline"))
|
|
1064
|
+
except ValueError as exc:
|
|
1065
|
+
return self._json(400, {"error": str(exc)})
|
|
1066
|
+
return self._json(200, {"report": report})
|
|
1067
|
+
if path == "/api/knowledge/pipeline":
|
|
1068
|
+
from core import knowledge_pipeline
|
|
1069
|
+
body = self._body() or {}
|
|
1070
|
+
try:
|
|
1071
|
+
report = knowledge_pipeline.ingest(
|
|
1072
|
+
body.get("source"), body.get("text"), body.get("metadata"),
|
|
1073
|
+
chunk_size=body.get("chunk_size", 1200),
|
|
1074
|
+
overlap=body.get("overlap", 120))
|
|
1075
|
+
except ValueError as exc:
|
|
1076
|
+
return self._json(400, {"error": str(exc)})
|
|
1077
|
+
return self._json(200, {"ok": True, "report": report})
|
|
975
1078
|
if path == "/api/hooks/run":
|
|
976
1079
|
return self._api_hook_run()
|
|
977
1080
|
if path == "/api/health/op":
|
|
@@ -1181,6 +1284,35 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
1181
1284
|
m = re.match(r"^/api/(tasks|runs)/([^/]+)/reveal$", path)
|
|
1182
1285
|
if m:
|
|
1183
1286
|
return self._api_reveal(m.group(1), m.group(2))
|
|
1287
|
+
m = re.match(r"^/api/runs/([^/]+)/checkpoints/restore$", path)
|
|
1288
|
+
if m:
|
|
1289
|
+
if not bool((self._body() or {}).get("confirm")):
|
|
1290
|
+
return self._json(400, {"error": "文件恢复会覆盖检查点记录的文件,请确认 confirm=true 后重试"})
|
|
1291
|
+
run = store.get_run(m.group(1))
|
|
1292
|
+
if not run:
|
|
1293
|
+
return self._json(404, {"error": "not found"})
|
|
1294
|
+
if run.get("status") not in ("done", "failed", "cancelled", "timeout"):
|
|
1295
|
+
return self._json(409, {"error": "任务仍在运行或排队;请先停止并等待终态后再恢复文件"})
|
|
1296
|
+
task = store.get_task(run.get("task_id")) or {}
|
|
1297
|
+
if not task.get("workdir"):
|
|
1298
|
+
return self._json(409, {"error": "任务工作目录不可用,拒绝恢复"})
|
|
1299
|
+
from core import checkpoints
|
|
1300
|
+
# Serialize the final safety check and file restore with run creation
|
|
1301
|
+
# and status transitions. Otherwise a new run can start on this task
|
|
1302
|
+
# after the terminal check and race with restoration in the same tree.
|
|
1303
|
+
with store.LOCK:
|
|
1304
|
+
current = store.get_run(m.group(1)) or {}
|
|
1305
|
+
task = store.get_task(current.get("task_id")) or {}
|
|
1306
|
+
latest = store.latest_run_by_task().get(current.get("task_id")) or {}
|
|
1307
|
+
terminal = ("done", "failed", "cancelled", "timeout")
|
|
1308
|
+
if current.get("status") not in terminal:
|
|
1309
|
+
return self._json(409, {"error": "任务状态已变化;请等待终态后再恢复文件"})
|
|
1310
|
+
task_active = task.get("status") in ("queued", "running")
|
|
1311
|
+
newer_run_active = latest.get("status") in ("queued", "running")
|
|
1312
|
+
if task_active or newer_run_active:
|
|
1313
|
+
return self._json(409, {"error": "该任务已有运行中的新任务;请等待其结束后再恢复文件"})
|
|
1314
|
+
return self._json(200, checkpoints.restore_files(
|
|
1315
|
+
m.group(1), task.get("workdir") or ""))
|
|
1184
1316
|
m = re.match(r"^/api/runs/([^/]+)/messages$", path)
|
|
1185
1317
|
if m:
|
|
1186
1318
|
return self._api_add_message(m.group(1))
|
|
@@ -1500,9 +1632,9 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
1500
1632
|
if path == "/api/settings":
|
|
1501
1633
|
view, err = settings.save(self._body())
|
|
1502
1634
|
if err:
|
|
1503
|
-
return self._json(400, {"error": err, "settings": view})
|
|
1635
|
+
return self._json(400, {"error": err, "settings": settings.public_view(view)})
|
|
1504
1636
|
n = jobs.configure(view["max_concurrent_jobs"])
|
|
1505
|
-
return self._json(200, {"ok": True, "settings": view, "workers": n})
|
|
1637
|
+
return self._json(200, {"ok": True, "settings": settings.public_view(view), "workers": n})
|
|
1506
1638
|
m = re.match(r"^/api/settings-v2/([a-z_-]+)$", path)
|
|
1507
1639
|
if m:
|
|
1508
1640
|
# schema 化设置写入口:{ops:[{op:"set",path,value}], expected_revision?}
|
|
@@ -2143,11 +2275,14 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
2143
2275
|
force = bool(body.get("force"))
|
|
2144
2276
|
force_confirmed = bool(body.get("force_confirmed"))
|
|
2145
2277
|
force_reason = str(body.get("force_reason") or "").strip()
|
|
2278
|
+
only = body.get("chapters") # 批量选择发布:章号数组(可省=全部待发)
|
|
2279
|
+
kwargs = {"only": only if isinstance(only, list) else None}
|
|
2280
|
+
if force or force_confirmed or force_reason:
|
|
2281
|
+
kwargs.update(force=force, force_confirmed=force_confirmed,
|
|
2282
|
+
force_reason=force_reason)
|
|
2146
2283
|
ok, err = pub_auto.publish_pending_async(
|
|
2147
2284
|
task_id, platform, auto_submit=bool(body.get("auto_submit")),
|
|
2148
|
-
**
|
|
2149
|
-
"force_reason": force_reason}
|
|
2150
|
-
if force or force_confirmed or force_reason else {}))
|
|
2285
|
+
**kwargs)
|
|
2151
2286
|
if not ok:
|
|
2152
2287
|
payload = {"error": err or "操作失败"}
|
|
2153
2288
|
if str(err or "").startswith("质量门禁拦截:"):
|
|
@@ -2988,8 +3123,16 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
2988
3123
|
'[{"q": "问题", "options": ["选项1", "选项2"]}]' % (ttype, goal[:200]))
|
|
2989
3124
|
try:
|
|
2990
3125
|
# 澄清是可选增强,不能占住创建请求;主流程有自己的执行超时。
|
|
3126
|
+
# 此调用只负责生成问题,不得继承完整执行工具权限或访问网络。
|
|
3127
|
+
from core import policy
|
|
3128
|
+
clarify_sandbox = policy.normalize_sandbox({
|
|
3129
|
+
"network": False,
|
|
3130
|
+
"disabled_tools": ["run_command", "write_file", "edit_file",
|
|
3131
|
+
"append_file", "fs_manage", "create_task",
|
|
3132
|
+
"read_tool_output"],
|
|
3133
|
+
}, os.getcwd() if hasattr(os, "getcwd") else ".")
|
|
2991
3134
|
res = builtin_agent.run(bi, prompt, os.getcwd() if hasattr(os, "getcwd") else ".",
|
|
2992
|
-
timeout=8)
|
|
3135
|
+
timeout=8, sandbox=clarify_sandbox)
|
|
2993
3136
|
import json as _json
|
|
2994
3137
|
arr = None
|
|
2995
3138
|
text = (res.get("text") or "").strip()
|
|
@@ -3024,10 +3167,13 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
3024
3167
|
POST /api/hooks/run,头 X-CodeBee-Token;体 {goal 必填, type, workdir,
|
|
3025
3168
|
context, title}。令牌取设置 hooks_token(data/settings.json,UI/文件均可
|
|
3026
3169
|
配置):已配置则必须精确匹配;未配置仅放行本机回环。任务链与 UI 完全相同。"""
|
|
3170
|
+
from core import webhooks
|
|
3027
3171
|
try:
|
|
3028
|
-
|
|
3172
|
+
cfg = settings.load()
|
|
3173
|
+
tok = str(cfg.get("hooks_token") or "")
|
|
3174
|
+
signing_secret = str(cfg.get("hooks_signing_secret") or "")
|
|
3029
3175
|
except Exception:
|
|
3030
|
-
tok = ""
|
|
3176
|
+
tok, signing_secret = "", ""
|
|
3031
3177
|
given = (self.headers.get("X-CodeBee-Token") or "").strip()
|
|
3032
3178
|
if tok:
|
|
3033
3179
|
if given != tok:
|
|
@@ -3037,6 +3183,18 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
3037
3183
|
if host not in ("127.0.0.1", "::1", "::ffff:127.0.0.1"):
|
|
3038
3184
|
return self._json(403, {
|
|
3039
3185
|
"error": "未配置 hooks.token,仅允许本机触发;请先在设置中配置令牌"})
|
|
3186
|
+
# Optional HMAC envelope for GitHub/GitLab-style integrations. A
|
|
3187
|
+
# configured secret makes the signature mandatory; token auth remains
|
|
3188
|
+
# useful for old local scripts when no secret is configured.
|
|
3189
|
+
if signing_secret:
|
|
3190
|
+
timestamp = self.headers.get("X-CodeBee-Timestamp") or ""
|
|
3191
|
+
signature = (self.headers.get("X-CodeBee-Signature") or
|
|
3192
|
+
self.headers.get("X-Hub-Signature-256") or "")
|
|
3193
|
+
signed = (webhooks.verify(signing_secret, self._raw_request_body(),
|
|
3194
|
+
signature, timestamp) if timestamp else
|
|
3195
|
+
webhooks.verify_raw(signing_secret, self._raw_request_body(), signature))
|
|
3196
|
+
if not signed:
|
|
3197
|
+
return self._json(401, {"error": "Webhook 签名无效或已过期"})
|
|
3040
3198
|
raw = self._body()
|
|
3041
3199
|
payload = {}
|
|
3042
3200
|
for k, cap in (("goal", 4000), ("context", 4000), ("type", 40),
|
|
@@ -3050,7 +3208,18 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
3050
3208
|
payload["type"] = "direct"
|
|
3051
3209
|
if not payload.get("title"):
|
|
3052
3210
|
payload["title"] = payload["goal"][:30]
|
|
3211
|
+
delivery_id = (self.headers.get("X-CodeBee-Delivery") or
|
|
3212
|
+
self.headers.get("X-Delivery-Id") or "").strip()
|
|
3213
|
+
if delivery_id:
|
|
3214
|
+
claimed, info = webhooks.claim_delivery(delivery_id)
|
|
3215
|
+
if not claimed:
|
|
3216
|
+
return self._json(200, {"ok": True, "deduplicated": True,
|
|
3217
|
+
"delivery_id": info.get("delivery_id")})
|
|
3053
3218
|
status, resp = self._create_and_start(payload)
|
|
3219
|
+
if delivery_id and status != 200:
|
|
3220
|
+
webhooks.release_delivery(delivery_id)
|
|
3221
|
+
if delivery_id:
|
|
3222
|
+
resp = dict(resp, delivery_id=delivery_id)
|
|
3054
3223
|
return self._json(status, resp)
|
|
3055
3224
|
|
|
3056
3225
|
def _enqueue_run(self, run_id, task_id, job):
|