codebee 0.1.74 → 0.1.77

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 (119) hide show
  1. package/CHANGELOG.md +628 -607
  2. package/LICENSE +21 -21
  3. package/README.md +507 -508
  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/aiflavor.py +197 -197
  13. package/app/core/analytics.py +38 -38
  14. package/app/core/artifacts.py +148 -148
  15. package/app/core/assets.py +110 -110
  16. package/app/core/attachments.py +623 -623
  17. package/app/core/audit.py +30 -30
  18. package/app/core/backup.py +470 -470
  19. package/app/core/beekeeper.py +183 -183
  20. package/app/core/benchstore.py +95 -95
  21. package/app/core/board.py +209 -209
  22. package/app/core/bookmeta.py +1036 -921
  23. package/app/core/bookmeta_catalog.py +180 -180
  24. package/app/core/branching.py +137 -137
  25. package/app/core/builtin_agent.py +1680 -1680
  26. package/app/core/capability.py +144 -144
  27. package/app/core/chaptersafe.py +32 -32
  28. package/app/core/cleanup.py +303 -303
  29. package/app/core/compaction.py +201 -201
  30. package/app/core/contracts.py +255 -255
  31. package/app/core/covergen.py +356 -356
  32. package/app/core/defectretro.py +43 -43
  33. package/app/core/diagnostics.py +114 -114
  34. package/app/core/dispatch.py +190 -190
  35. package/app/core/dispatch_log.py +128 -128
  36. package/app/core/env_scrub.py +85 -85
  37. package/app/core/errorlog.py +137 -137
  38. package/app/core/evaluation.py +174 -174
  39. package/app/core/failure_notify.py +290 -290
  40. package/app/core/goal_service.py +167 -167
  41. package/app/core/health.py +389 -387
  42. package/app/core/history.py +38 -38
  43. package/app/core/hooks.py +162 -162
  44. package/app/core/jobs.py +22 -0
  45. package/app/core/manager.py +2232 -2232
  46. package/app/core/mocks.py +64 -64
  47. package/app/core/notify.py +201 -201
  48. package/app/core/novel_quality.py +189 -189
  49. package/app/core/openapi.py +154 -154
  50. package/app/core/operations.py +228 -228
  51. package/app/core/paihang.py +145 -145
  52. package/app/core/pipeline.py +189 -18
  53. package/app/core/planner.py +88 -0
  54. package/app/core/portguard.py +111 -111
  55. package/app/core/presence.py +77 -77
  56. package/app/core/preview.py +293 -293
  57. package/app/core/prompt_guard.py +78 -78
  58. package/app/core/publish/__init__.py +7 -7
  59. package/app/core/publish/browser.py +655 -655
  60. package/app/core/publish/fanqie.py +165 -165
  61. package/app/core/publish/flow.py +640 -640
  62. package/app/core/publish/flows-fanqie-calibrated.json +336 -336
  63. package/app/core/publish/manager.py +1118 -1118
  64. package/app/core/publish/qimao.py +176 -176
  65. package/app/core/publish/ws.py +139 -139
  66. package/app/core/quality_gate.py +194 -194
  67. package/app/core/redact.py +38 -38
  68. package/app/core/repeat_guard.py +129 -129
  69. package/app/core/retrieval.py +137 -137
  70. package/app/core/router.py +398 -398
  71. package/app/core/runner.py +2238 -2225
  72. package/app/core/selfupdate.py +384 -384
  73. package/app/core/session_log.py +161 -161
  74. package/app/core/sessions.py +312 -312
  75. package/app/core/settings_schema.py +270 -270
  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/skills.py +1 -1
  85. package/app/core/step_runner.py +132 -132
  86. package/app/core/story_tracking.py +275 -241
  87. package/app/core/task_compile.py +199 -199
  88. package/app/core/telemetry.py +292 -292
  89. package/app/core/tlsctx.py +64 -64
  90. package/app/core/token_meter.py +147 -147
  91. package/app/core/tracing.py +144 -144
  92. package/app/core/upstream.py +41 -41
  93. package/app/core/usage.py +1035 -1035
  94. package/app/core/volumes.py +389 -389
  95. package/app/core/wxdigest.py +858 -858
  96. package/app/core/zentao.py +2692 -2692
  97. package/app/main.py +59 -0
  98. package/app/mcp_server.py +244 -244
  99. package/app/output/_createbook_body.json +1 -0
  100. package/app/output/_probe_fq_click.py +50 -0
  101. package/app/output/_probe_fq_form.py +42 -0
  102. package/app/output/_probe_fq_modal.py +55 -0
  103. package/app/output/_probe_fq_points.py +71 -0
  104. package/app/output/_probe_fq_submit.py +34 -0
  105. package/app/output/_probe_fq_watch.py +62 -0
  106. package/app/pick_dialog.py +110 -110
  107. package/app/ui/app.js +108 -12
  108. package/app/ui/artdiff.js +74 -74
  109. package/app/ui/board.html +162 -162
  110. package/app/ui/board.js +305 -305
  111. package/app/ui/i18n.js +15 -0
  112. package/app/ui/icons/bee.svg +79 -79
  113. package/app/ui/index.html +4 -0
  114. package/app/ui/manifest.json +16 -16
  115. package/app/ui/qrcode.js +2297 -2297
  116. package/app/ui/style.css +7086 -7070
  117. package/bin/tutti.js +147 -147
  118. package/package.json +48 -48
  119. package/tools/wx_reader.py +261 -261
@@ -1,144 +1,144 @@
1
- # -*- coding: utf-8 -*-
2
- """Run/step/event tracing primitives.
3
-
4
- The shape intentionally follows OpenTelemetry/OpenInference's trace -> span
5
- relationship without adding a third-party dependency. It is a read model of
6
- the existing run and dispatch ledgers: no prompt, stdout, stderr, credentials,
7
- or artifact contents are copied into the trace.
8
- """
9
- from __future__ import annotations
10
-
11
- import hashlib
12
- from datetime import datetime
13
-
14
- from .redact import scrub_text
15
-
16
-
17
- SCHEMA_VERSION = 1
18
-
19
-
20
- def _stable(prefix: str, *parts: object, length: int = 32) -> str:
21
- raw = prefix + ":" + ":".join(str(part or "") for part in parts)
22
- return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:length]
23
-
24
-
25
- def trace_id(run_id: object) -> str:
26
- """Return a deterministic 128-bit trace id for a run."""
27
- return _stable("codebee.trace", run_id, length=32)
28
-
29
-
30
- def root_span_id(run_id: object) -> str:
31
- return _stable("codebee.root", run_id, length=16)
32
-
33
-
34
- def span_id(run_id: object, step_no: object) -> str:
35
- return _stable("codebee.step", run_id, step_no, length=16)
36
-
37
-
38
- def event_id(run_id: object, event: dict) -> str:
39
- """Stable event id used for deduplication during replay."""
40
- return _stable(
41
- "codebee.event",
42
- run_id,
43
- event.get("ts"),
44
- event.get("phase"),
45
- event.get("role"),
46
- event.get("selected"),
47
- event.get("result"),
48
- length=24,
49
- )
50
-
51
-
52
- def _epoch(value):
53
- if value is None or isinstance(value, (int, float)):
54
- return value
55
- text = str(value).strip()
56
- if not text:
57
- return None
58
- try:
59
- return datetime.fromisoformat(text.replace("Z", "+00:00")).timestamp()
60
- except (TypeError, ValueError, OverflowError):
61
- return None
62
-
63
-
64
- def _duration(step: dict):
65
- try:
66
- value = float(step.get("duration_s"))
67
- return round(max(0.0, value), 3)
68
- except (TypeError, ValueError):
69
- started = _epoch(step.get("started_at_epoch"))
70
- ended = _epoch(step.get("ended_at_epoch"))
71
- if started is not None and ended is not None:
72
- return round(max(0.0, ended - started), 3)
73
- return None
74
-
75
-
76
- def _safe_event(event: dict, run_id: str) -> dict:
77
- """Keep only routing metadata from a dispatch event."""
78
- allowed = (
79
- "ts", "task_id", "task_type", "difficulty", "role", "phase",
80
- "selected", "participants", "candidates", "fallback",
81
- "selection_reason", "result", "verify_pass", "review_pass",
82
- "trace_id", "span_id", "parent_event_id", "event_id",
83
- )
84
- item = {key: event[key] for key in allowed if key in event}
85
- item.setdefault("trace_id", trace_id(run_id))
86
- item.setdefault("event_id", event_id(run_id, item))
87
- return _redact(item)
88
-
89
-
90
- def _redact(value, limit=600):
91
- """Redact strings even when a legacy event predates dispatch_log scrub."""
92
- if isinstance(value, dict):
93
- return {key: _redact(item, 240) for key, item in value.items()}
94
- if isinstance(value, list):
95
- return [_redact(item, 240) for item in value]
96
- if isinstance(value, str):
97
- return scrub_text(value, limit=limit)
98
- return value
99
-
100
-
101
- def build_trace(run: dict, events=()) -> dict:
102
- """Build a stable, redacted trace view from an existing run dictionary."""
103
- run_id = str(run.get("id") or "")
104
- tid = str(run.get("trace_id") or trace_id(run_id))
105
- rid = str(run.get("root_span_id") or root_span_id(run_id))
106
- spans = []
107
- for step in run.get("steps") or ():
108
- if not isinstance(step, dict):
109
- continue
110
- try:
111
- number = int(step.get("n"))
112
- except (TypeError, ValueError):
113
- continue
114
- span = {
115
- "trace_id": tid,
116
- "span_id": str(step.get("span_id") or span_id(run_id, number)),
117
- "parent_span_id": str(step.get("parent_span_id") or rid),
118
- "name": "agent.%s" % str(step.get("role") or "step")[:80],
119
- "step": number,
120
- "status": str(step.get("status") or "unknown"),
121
- "agent": str(step.get("agent") or "")[:80],
122
- "model": str(step.get("model") or "")[:120],
123
- "provider": str(step.get("provider") or "")[:120],
124
- "started_at": step.get("started_at_epoch"),
125
- "ended_at": step.get("ended_at_epoch"),
126
- "duration_s": _duration(step),
127
- "tokens": step.get("tokens", 0),
128
- "cost_usd": step.get("cost_usd", 0.0),
129
- "exit_code": step.get("exit_code"),
130
- }
131
- spans.append(span)
132
- safe_events = [_safe_event(item, run_id) for item in (events or ())
133
- if isinstance(item, dict)]
134
- return {
135
- "version": SCHEMA_VERSION,
136
- "trace_id": tid,
137
- "root_span_id": rid,
138
- "run_id": run_id,
139
- "status": str(run.get("status") or "unknown"),
140
- "started_at": run.get("started_at"),
141
- "ended_at": run.get("ended_at"),
142
- "spans": spans,
143
- "events": safe_events,
144
- }
1
+ # -*- coding: utf-8 -*-
2
+ """Run/step/event tracing primitives.
3
+
4
+ The shape intentionally follows OpenTelemetry/OpenInference's trace -> span
5
+ relationship without adding a third-party dependency. It is a read model of
6
+ the existing run and dispatch ledgers: no prompt, stdout, stderr, credentials,
7
+ or artifact contents are copied into the trace.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ from datetime import datetime
13
+
14
+ from .redact import scrub_text
15
+
16
+
17
+ SCHEMA_VERSION = 1
18
+
19
+
20
+ def _stable(prefix: str, *parts: object, length: int = 32) -> str:
21
+ raw = prefix + ":" + ":".join(str(part or "") for part in parts)
22
+ return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:length]
23
+
24
+
25
+ def trace_id(run_id: object) -> str:
26
+ """Return a deterministic 128-bit trace id for a run."""
27
+ return _stable("codebee.trace", run_id, length=32)
28
+
29
+
30
+ def root_span_id(run_id: object) -> str:
31
+ return _stable("codebee.root", run_id, length=16)
32
+
33
+
34
+ def span_id(run_id: object, step_no: object) -> str:
35
+ return _stable("codebee.step", run_id, step_no, length=16)
36
+
37
+
38
+ def event_id(run_id: object, event: dict) -> str:
39
+ """Stable event id used for deduplication during replay."""
40
+ return _stable(
41
+ "codebee.event",
42
+ run_id,
43
+ event.get("ts"),
44
+ event.get("phase"),
45
+ event.get("role"),
46
+ event.get("selected"),
47
+ event.get("result"),
48
+ length=24,
49
+ )
50
+
51
+
52
+ def _epoch(value):
53
+ if value is None or isinstance(value, (int, float)):
54
+ return value
55
+ text = str(value).strip()
56
+ if not text:
57
+ return None
58
+ try:
59
+ return datetime.fromisoformat(text.replace("Z", "+00:00")).timestamp()
60
+ except (TypeError, ValueError, OverflowError):
61
+ return None
62
+
63
+
64
+ def _duration(step: dict):
65
+ try:
66
+ value = float(step.get("duration_s"))
67
+ return round(max(0.0, value), 3)
68
+ except (TypeError, ValueError):
69
+ started = _epoch(step.get("started_at_epoch"))
70
+ ended = _epoch(step.get("ended_at_epoch"))
71
+ if started is not None and ended is not None:
72
+ return round(max(0.0, ended - started), 3)
73
+ return None
74
+
75
+
76
+ def _safe_event(event: dict, run_id: str) -> dict:
77
+ """Keep only routing metadata from a dispatch event."""
78
+ allowed = (
79
+ "ts", "task_id", "task_type", "difficulty", "role", "phase",
80
+ "selected", "participants", "candidates", "fallback",
81
+ "selection_reason", "result", "verify_pass", "review_pass",
82
+ "trace_id", "span_id", "parent_event_id", "event_id",
83
+ )
84
+ item = {key: event[key] for key in allowed if key in event}
85
+ item.setdefault("trace_id", trace_id(run_id))
86
+ item.setdefault("event_id", event_id(run_id, item))
87
+ return _redact(item)
88
+
89
+
90
+ def _redact(value, limit=600):
91
+ """Redact strings even when a legacy event predates dispatch_log scrub."""
92
+ if isinstance(value, dict):
93
+ return {key: _redact(item, 240) for key, item in value.items()}
94
+ if isinstance(value, list):
95
+ return [_redact(item, 240) for item in value]
96
+ if isinstance(value, str):
97
+ return scrub_text(value, limit=limit)
98
+ return value
99
+
100
+
101
+ def build_trace(run: dict, events=()) -> dict:
102
+ """Build a stable, redacted trace view from an existing run dictionary."""
103
+ run_id = str(run.get("id") or "")
104
+ tid = str(run.get("trace_id") or trace_id(run_id))
105
+ rid = str(run.get("root_span_id") or root_span_id(run_id))
106
+ spans = []
107
+ for step in run.get("steps") or ():
108
+ if not isinstance(step, dict):
109
+ continue
110
+ try:
111
+ number = int(step.get("n"))
112
+ except (TypeError, ValueError):
113
+ continue
114
+ span = {
115
+ "trace_id": tid,
116
+ "span_id": str(step.get("span_id") or span_id(run_id, number)),
117
+ "parent_span_id": str(step.get("parent_span_id") or rid),
118
+ "name": "agent.%s" % str(step.get("role") or "step")[:80],
119
+ "step": number,
120
+ "status": str(step.get("status") or "unknown"),
121
+ "agent": str(step.get("agent") or "")[:80],
122
+ "model": str(step.get("model") or "")[:120],
123
+ "provider": str(step.get("provider") or "")[:120],
124
+ "started_at": step.get("started_at_epoch"),
125
+ "ended_at": step.get("ended_at_epoch"),
126
+ "duration_s": _duration(step),
127
+ "tokens": step.get("tokens", 0),
128
+ "cost_usd": step.get("cost_usd", 0.0),
129
+ "exit_code": step.get("exit_code"),
130
+ }
131
+ spans.append(span)
132
+ safe_events = [_safe_event(item, run_id) for item in (events or ())
133
+ if isinstance(item, dict)]
134
+ return {
135
+ "version": SCHEMA_VERSION,
136
+ "trace_id": tid,
137
+ "root_span_id": rid,
138
+ "run_id": run_id,
139
+ "status": str(run.get("status") or "unknown"),
140
+ "started_at": run.get("started_at"),
141
+ "ended_at": run.get("ended_at"),
142
+ "spans": spans,
143
+ "events": safe_events,
144
+ }
@@ -1,41 +1,41 @@
1
- # -*- coding: utf-8 -*-
2
- """Shared upstream identity normalization for routing and retry guards."""
3
- from __future__ import annotations
4
-
5
- import ipaddress
6
- from urllib.parse import urlsplit
7
-
8
-
9
- def normalize_upstream(value, default_scheme="https"):
10
- """Return a stable host[:port] identity, without URL path or credentials.
11
-
12
- Host names are case-insensitive and a trailing dot is equivalent to the
13
- non-dotted form. Default HTTP(S) ports are omitted so equivalent URLs share
14
- one failure/permission bucket. Non-default ports remain distinct.
15
- """
16
- raw = str(value or "").strip()
17
- if not raw:
18
- return ""
19
- candidate = raw if "://" in raw else "%s://%s" % (default_scheme, raw)
20
- try:
21
- parsed = urlsplit(candidate)
22
- host = parsed.hostname
23
- if not host:
24
- return ""
25
- host = host.rstrip(".").lower()
26
- ip_host = None
27
- try:
28
- ip_host = ipaddress.ip_address(host)
29
- host = ip_host.compressed.lower()
30
- except ValueError:
31
- pass
32
- port = parsed.port
33
- scheme = (parsed.scheme or default_scheme).lower()
34
- if port is not None and not ((scheme == "https" and port == 443)
35
- or (scheme == "http" and port == 80)):
36
- if getattr(ip_host, "version", None) == 6:
37
- return "[%s]:%d" % (host, port)
38
- return "%s:%d" % (host, port)
39
- return host
40
- except (TypeError, ValueError):
41
- return ""
1
+ # -*- coding: utf-8 -*-
2
+ """Shared upstream identity normalization for routing and retry guards."""
3
+ from __future__ import annotations
4
+
5
+ import ipaddress
6
+ from urllib.parse import urlsplit
7
+
8
+
9
+ def normalize_upstream(value, default_scheme="https"):
10
+ """Return a stable host[:port] identity, without URL path or credentials.
11
+
12
+ Host names are case-insensitive and a trailing dot is equivalent to the
13
+ non-dotted form. Default HTTP(S) ports are omitted so equivalent URLs share
14
+ one failure/permission bucket. Non-default ports remain distinct.
15
+ """
16
+ raw = str(value or "").strip()
17
+ if not raw:
18
+ return ""
19
+ candidate = raw if "://" in raw else "%s://%s" % (default_scheme, raw)
20
+ try:
21
+ parsed = urlsplit(candidate)
22
+ host = parsed.hostname
23
+ if not host:
24
+ return ""
25
+ host = host.rstrip(".").lower()
26
+ ip_host = None
27
+ try:
28
+ ip_host = ipaddress.ip_address(host)
29
+ host = ip_host.compressed.lower()
30
+ except ValueError:
31
+ pass
32
+ port = parsed.port
33
+ scheme = (parsed.scheme or default_scheme).lower()
34
+ if port is not None and not ((scheme == "https" and port == 443)
35
+ or (scheme == "http" and port == 80)):
36
+ if getattr(ip_host, "version", None) == 6:
37
+ return "[%s]:%d" % (host, port)
38
+ return "%s:%d" % (host, port)
39
+ return host
40
+ except (TypeError, ValueError):
41
+ return ""