codebee 0.1.68 → 0.1.69

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 (106) hide show
  1. package/CHANGELOG.md +543 -543
  2. package/LICENSE +21 -21
  3. package/README.md +10 -0
  4. package/app/core/acceptance.py +100 -100
  5. package/app/core/aiflavor.py +197 -197
  6. package/app/core/artifacts.py +148 -148
  7. package/app/core/assets.py +110 -110
  8. package/app/core/attachments.py +623 -623
  9. package/app/core/automation.py +758 -758
  10. package/app/core/backup.py +470 -470
  11. package/app/core/beekeeper.py +183 -183
  12. package/app/core/board.py +209 -209
  13. package/app/core/bookmeta.py +930 -930
  14. package/app/core/bookmeta_catalog.py +180 -180
  15. package/app/core/branching.py +137 -137
  16. package/app/core/builtin_agent.py +1234 -1234
  17. package/app/core/capability.py +144 -144
  18. package/app/core/chaptersafe.py +32 -32
  19. package/app/core/cleanup.py +303 -303
  20. package/app/core/compaction.py +201 -201
  21. package/app/core/covergen.py +356 -356
  22. package/app/core/defectretro.py +43 -43
  23. package/app/core/diagnostics.py +114 -114
  24. package/app/core/dispatch.py +173 -173
  25. package/app/core/dispatch_log.py +128 -128
  26. package/app/core/env_scrub.py +85 -85
  27. package/app/core/error_codes.py +23 -23
  28. package/app/core/errorlog.py +137 -137
  29. package/app/core/evaluation.py +174 -174
  30. package/app/core/failure_notify.py +290 -290
  31. package/app/core/goal_service.py +167 -167
  32. package/app/core/health.py +374 -374
  33. package/app/core/history.py +38 -38
  34. package/app/core/hooks.py +162 -162
  35. package/app/core/jobs.py +29 -29
  36. package/app/core/manager.py +2122 -2122
  37. package/app/core/market.py +362 -362
  38. package/app/core/mocks.py +64 -64
  39. package/app/core/modelhub.py +8 -8
  40. package/app/core/notify.py +106 -106
  41. package/app/core/operations.py +228 -228
  42. package/app/core/paihang.py +138 -138
  43. package/app/core/paths.py +2 -2
  44. package/app/core/portguard.py +111 -111
  45. package/app/core/preview.py +293 -293
  46. package/app/core/publish/__init__.py +7 -7
  47. package/app/core/publish/auto.py +367 -367
  48. package/app/core/publish/browser.py +539 -539
  49. package/app/core/publish/fanqie.py +159 -159
  50. package/app/core/publish/flow.py +440 -440
  51. package/app/core/publish/flows-fanqie-calibrated.json +332 -332
  52. package/app/core/publish/ledger.py +203 -203
  53. package/app/core/publish/manager.py +692 -692
  54. package/app/core/publish/qimao.py +141 -141
  55. package/app/core/publish/ws.py +139 -139
  56. package/app/core/redact.py +38 -38
  57. package/app/core/repeat_guard.py +129 -129
  58. package/app/core/router.py +398 -398
  59. package/app/core/runner.py +2067 -2067
  60. package/app/core/selfupdate.py +384 -384
  61. package/app/core/session_log.py +161 -161
  62. package/app/core/sessions.py +312 -312
  63. package/app/core/settings.py +149 -149
  64. package/app/core/settings_schema.py +262 -262
  65. package/app/core/share_page.py +114 -114
  66. package/app/core/skill_scan.py +122 -122
  67. package/app/core/skillpacks/fanqie-novel.md +80 -80
  68. package/app/core/skillpacks/market/character-bible.md +66 -66
  69. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  70. package/app/core/skillpacks/market/git-workflow.md +57 -57
  71. package/app/core/skillpacks/market/release-notes.md +72 -72
  72. package/app/core/skillpacks/market/weekly-report.md +71 -71
  73. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  74. package/app/core/step_runner.py +132 -132
  75. package/app/core/store.py +75 -75
  76. package/app/core/task_compile.py +199 -199
  77. package/app/core/telemetry.py +292 -292
  78. package/app/core/tlsctx.py +64 -64
  79. package/app/core/token_meter.py +147 -147
  80. package/app/core/tracing.py +144 -144
  81. package/app/core/upstream.py +41 -41
  82. package/app/core/usage.py +862 -862
  83. package/app/core/volumes.py +389 -389
  84. package/app/core/wxdigest.py +858 -858
  85. package/app/core/zentao.py +2465 -2465
  86. package/app/main.py +2981 -2981
  87. package/app/pick_dialog.py +110 -110
  88. package/app/ui/app.js +14399 -14399
  89. package/app/ui/artdiff.js +74 -74
  90. package/app/ui/board.html +162 -162
  91. package/app/ui/board.js +305 -305
  92. package/app/ui/i18n.js +2770 -2770
  93. package/app/ui/icons/bee.svg +79 -79
  94. package/app/ui/manifest.json +16 -16
  95. package/app/ui/qrcode.js +2297 -2297
  96. package/app/ui/style.css +16 -16
  97. package/bin/tutti.js +147 -147
  98. package/package.json +46 -46
  99. package/tools/wx_reader.py +261 -261
  100. package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +0 -88
  101. package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +0 -126
  102. package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
  103. package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +0 -210
  104. package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
  105. package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
  106. package/app/.playwright-cli/run-detail-after.png +0 -0
@@ -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 ""