codebee 0.1.95 → 0.1.96
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 +725 -720
- package/LICENSE +21 -21
- package/README.md +515 -509
- package/app/core/acceptance.py +100 -100
- package/app/core/agent_context.py +100 -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 +209 -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 +1680 -1680
- package/app/core/capability.py +144 -144
- package/app/core/chaptersafe.py +32 -32
- package/app/core/checkpoints.py +143 -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/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 +43 -19
- package/app/core/planner.py +17 -11
- package/app/core/policy.py +52 -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/prompt_guard.py +78 -78
- package/app/core/publish/__init__.py +7 -7
- package/app/core/publish/auto.py +26 -3
- package/app/core/publish/browser.py +655 -655
- package/app/core/publish/chaprows.py +138 -0
- package/app/core/publish/fanqie.py +210 -165
- package/app/core/publish/flow.py +753 -640
- package/app/core/publish/flows-fanqie-calibrated.json +349 -336
- package/app/core/publish/ledger.py +13 -1
- 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 +2238 -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 +20 -0
- package/app/core/settings_schema.py +270 -270
- 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 +9 -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/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 +858 -858
- package/app/core/zentao.py +2692 -2692
- package/app/main.py +104 -9
- package/app/mcp_server.py +244 -244
- package/app/pick_dialog.py +110 -110
- package/app/ui/app.js +286 -18
- 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/board.html +162 -162
- package/app/ui/board.js +305 -305
- package/app/ui/i18n.js +33 -0
- package/app/ui/icons/bee.svg +79 -79
- package/app/ui/index.html +32 -3
- package/app/ui/manifest.json +16 -16
- package/app/ui/qrcode.js +2297 -2297
- package/app/ui/style.css +196 -2
- package/bin/tutti.js +147 -147
- package/package.json +48 -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/core/session_log.py
CHANGED
|
@@ -1,162 +1,162 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""Surface 会话日志:只追加事件流 + surface 派生模型输入。
|
|
3
|
-
|
|
4
|
-
设计稿:docs/migration/02-context-compaction.md §1A。
|
|
5
|
-
参考 dsh packages/core/session/src/types.ts:222-271(SessionEvent)与
|
|
6
|
-
surface.ts:140-167(append 同步校验 + replace generation)。
|
|
7
|
-
|
|
8
|
-
核心不变量:
|
|
9
|
-
1. 只追加:append 是唯一写入口,seq 单调,写入即落盘(JSONL)。
|
|
10
|
-
2. 模型可见即已记录:模型输入一律由 derive_messages() 从日志折叠派生,
|
|
11
|
-
不存在"日志外的模型输入"。
|
|
12
|
-
3. surface replace:压缩通过 surface_op={"op":"replace",start,end} 把一段
|
|
13
|
-
事件折叠为单条摘要消息,replace_generation 单调递增(1D 重试守门用)。
|
|
14
|
-
"""
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
import json
|
|
18
|
-
import threading
|
|
19
|
-
import time
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class SessionEvent:
|
|
23
|
-
__slots__ = ("seq", "type", "data", "ts", "surface_op", "turn_id")
|
|
24
|
-
|
|
25
|
-
def __init__(self, seq, ev_type, data, ts=None, surface_op=None, turn_id=None):
|
|
26
|
-
self.seq = seq
|
|
27
|
-
self.type = ev_type
|
|
28
|
-
self.data = data
|
|
29
|
-
self.ts = ts if ts is not None else time.time()
|
|
30
|
-
self.surface_op = surface_op
|
|
31
|
-
self.turn_id = turn_id
|
|
32
|
-
|
|
33
|
-
def to_dict(self):
|
|
34
|
-
d = {"seq": self.seq, "type": self.type, "data": self.data, "ts": self.ts}
|
|
35
|
-
if self.surface_op is not None:
|
|
36
|
-
d["surface_op"] = self.surface_op
|
|
37
|
-
if self.turn_id:
|
|
38
|
-
d["turn_id"] = self.turn_id
|
|
39
|
-
return d
|
|
40
|
-
|
|
41
|
-
@classmethod
|
|
42
|
-
def from_dict(cls, d):
|
|
43
|
-
return cls(d["seq"], d["type"], d.get("data") or {},
|
|
44
|
-
ts=d.get("ts"), surface_op=d.get("surface_op"),
|
|
45
|
-
turn_id=d.get("turn_id"))
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# 进入模型 surface 的事件类型(其余仅记录,不派生)
|
|
49
|
-
_SURFACE_TYPES = ("system_message", "user_message", "assistant_message")
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
class Session:
|
|
53
|
-
"""一次 run 的会话日志。线程安全;落盘路径缺省不持久化(仅内存)。"""
|
|
54
|
-
|
|
55
|
-
def __init__(self, run_id: str, store_path=None):
|
|
56
|
-
self.run_id = run_id
|
|
57
|
-
self.store_path = str(store_path) if store_path else None
|
|
58
|
-
self._lock = threading.Lock()
|
|
59
|
-
self._events = []
|
|
60
|
-
self._seq = 0
|
|
61
|
-
self._replace_generation = 0
|
|
62
|
-
# surface 视图:list[SessionEvent](已折叠);惰性重建
|
|
63
|
-
self._surface = []
|
|
64
|
-
self._surface_dirty = True
|
|
65
|
-
if self.store_path:
|
|
66
|
-
self._load()
|
|
67
|
-
|
|
68
|
-
# ---- 写入 ----
|
|
69
|
-
|
|
70
|
-
def append(self, ev_type, data, *, surface_op=None, turn_id=None):
|
|
71
|
-
"""追加事件。surface_op:
|
|
72
|
-
None -> 追加进 surface(若是 surface 类型)
|
|
73
|
-
"shadow" -> 仅记录,不进 surface(审计/中间产物)
|
|
74
|
-
{"op":"replace","start_seq":N,"end_seq":M} -> 把 [N,M] 折叠为本条
|
|
75
|
-
"""
|
|
76
|
-
with self._lock:
|
|
77
|
-
self._seq += 1
|
|
78
|
-
ev = SessionEvent(self._seq, ev_type, data,
|
|
79
|
-
surface_op=surface_op, turn_id=turn_id)
|
|
80
|
-
self._events.append(ev)
|
|
81
|
-
if isinstance(surface_op, dict) and surface_op.get("op") == "replace":
|
|
82
|
-
self._replace_generation += 1
|
|
83
|
-
self._surface_dirty = True
|
|
84
|
-
self._persist_one(ev)
|
|
85
|
-
return ev
|
|
86
|
-
|
|
87
|
-
# ---- 派生 ----
|
|
88
|
-
|
|
89
|
-
def _fold(self):
|
|
90
|
-
"""按事件序重放 surface。replace 折叠 [start_seq, end_seq] 区间为该条摘要。"""
|
|
91
|
-
surface = [] # list[SessionEvent]
|
|
92
|
-
for ev in self._events:
|
|
93
|
-
op = ev.surface_op
|
|
94
|
-
if op == "shadow":
|
|
95
|
-
continue
|
|
96
|
-
if isinstance(op, dict) and op.get("op") == "replace":
|
|
97
|
-
start, end = op["start_seq"], op["end_seq"]
|
|
98
|
-
surface = [e for e in surface if not (start <= e.seq <= end)]
|
|
99
|
-
surface.append(ev)
|
|
100
|
-
continue
|
|
101
|
-
if ev.type in _SURFACE_TYPES:
|
|
102
|
-
surface.append(ev)
|
|
103
|
-
return surface
|
|
104
|
-
|
|
105
|
-
def derive_messages(self):
|
|
106
|
-
"""模型输入消息列表 [{role, content}]。system 节点去重合并(保留最后一条同 role)。"""
|
|
107
|
-
with self._lock:
|
|
108
|
-
if self._surface_dirty:
|
|
109
|
-
self._surface = self._fold()
|
|
110
|
-
self._surface_dirty = False
|
|
111
|
-
snap = list(self._surface)
|
|
112
|
-
msgs = []
|
|
113
|
-
for ev in snap:
|
|
114
|
-
role = "system" if ev.type == "system_message" else (
|
|
115
|
-
"assistant" if ev.type == "assistant_message" else "user")
|
|
116
|
-
msgs.append({"role": role, "content": str(ev.data.get("content", "")),
|
|
117
|
-
"seq": ev.seq})
|
|
118
|
-
# 同 role 连续合并(CLI 提示词偏好单条 system/user)
|
|
119
|
-
merged = []
|
|
120
|
-
for m in msgs:
|
|
121
|
-
if merged and merged[-1]["role"] == m["role"]:
|
|
122
|
-
merged[-1]["content"] += "\n\n" + m["content"]
|
|
123
|
-
merged[-1]["seq"] = m["seq"]
|
|
124
|
-
else:
|
|
125
|
-
merged.append(dict(m))
|
|
126
|
-
return merged
|
|
127
|
-
|
|
128
|
-
def replace_generation(self):
|
|
129
|
-
with self._lock:
|
|
130
|
-
return self._replace_generation
|
|
131
|
-
|
|
132
|
-
def events(self):
|
|
133
|
-
with self._lock:
|
|
134
|
-
return list(self._events)
|
|
135
|
-
|
|
136
|
-
# ---- 持久化 ----
|
|
137
|
-
|
|
138
|
-
def _persist_one(self, ev):
|
|
139
|
-
if not self.store_path:
|
|
140
|
-
return
|
|
141
|
-
with open(self.store_path, "a", encoding="utf-8") as f:
|
|
142
|
-
f.write(json.dumps(ev.to_dict(), ensure_ascii=False) + "\n")
|
|
143
|
-
|
|
144
|
-
def _load(self):
|
|
145
|
-
"""启动恢复:逐行读 JSONL,坏行跳过(lazy skip)。"""
|
|
146
|
-
try:
|
|
147
|
-
with open(self.store_path, encoding="utf-8") as f:
|
|
148
|
-
for line in f:
|
|
149
|
-
line = line.strip()
|
|
150
|
-
if not line.startswith("{"):
|
|
151
|
-
continue
|
|
152
|
-
try:
|
|
153
|
-
self._events.append(SessionEvent.from_dict(json.loads(line)))
|
|
154
|
-
except Exception:
|
|
155
|
-
continue
|
|
156
|
-
if self._events:
|
|
157
|
-
self._seq = max(e.seq for e in self._events)
|
|
158
|
-
self._replace_generation = sum(
|
|
159
|
-
1 for e in self._events
|
|
160
|
-
if isinstance(e.surface_op, dict) and e.surface_op.get("op") == "replace")
|
|
161
|
-
except OSError:
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Surface 会话日志:只追加事件流 + surface 派生模型输入。
|
|
3
|
+
|
|
4
|
+
设计稿:docs/migration/02-context-compaction.md §1A。
|
|
5
|
+
参考 dsh packages/core/session/src/types.ts:222-271(SessionEvent)与
|
|
6
|
+
surface.ts:140-167(append 同步校验 + replace generation)。
|
|
7
|
+
|
|
8
|
+
核心不变量:
|
|
9
|
+
1. 只追加:append 是唯一写入口,seq 单调,写入即落盘(JSONL)。
|
|
10
|
+
2. 模型可见即已记录:模型输入一律由 derive_messages() 从日志折叠派生,
|
|
11
|
+
不存在"日志外的模型输入"。
|
|
12
|
+
3. surface replace:压缩通过 surface_op={"op":"replace",start,end} 把一段
|
|
13
|
+
事件折叠为单条摘要消息,replace_generation 单调递增(1D 重试守门用)。
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import threading
|
|
19
|
+
import time
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SessionEvent:
|
|
23
|
+
__slots__ = ("seq", "type", "data", "ts", "surface_op", "turn_id")
|
|
24
|
+
|
|
25
|
+
def __init__(self, seq, ev_type, data, ts=None, surface_op=None, turn_id=None):
|
|
26
|
+
self.seq = seq
|
|
27
|
+
self.type = ev_type
|
|
28
|
+
self.data = data
|
|
29
|
+
self.ts = ts if ts is not None else time.time()
|
|
30
|
+
self.surface_op = surface_op
|
|
31
|
+
self.turn_id = turn_id
|
|
32
|
+
|
|
33
|
+
def to_dict(self):
|
|
34
|
+
d = {"seq": self.seq, "type": self.type, "data": self.data, "ts": self.ts}
|
|
35
|
+
if self.surface_op is not None:
|
|
36
|
+
d["surface_op"] = self.surface_op
|
|
37
|
+
if self.turn_id:
|
|
38
|
+
d["turn_id"] = self.turn_id
|
|
39
|
+
return d
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def from_dict(cls, d):
|
|
43
|
+
return cls(d["seq"], d["type"], d.get("data") or {},
|
|
44
|
+
ts=d.get("ts"), surface_op=d.get("surface_op"),
|
|
45
|
+
turn_id=d.get("turn_id"))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# 进入模型 surface 的事件类型(其余仅记录,不派生)
|
|
49
|
+
_SURFACE_TYPES = ("system_message", "user_message", "assistant_message")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Session:
|
|
53
|
+
"""一次 run 的会话日志。线程安全;落盘路径缺省不持久化(仅内存)。"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, run_id: str, store_path=None):
|
|
56
|
+
self.run_id = run_id
|
|
57
|
+
self.store_path = str(store_path) if store_path else None
|
|
58
|
+
self._lock = threading.Lock()
|
|
59
|
+
self._events = []
|
|
60
|
+
self._seq = 0
|
|
61
|
+
self._replace_generation = 0
|
|
62
|
+
# surface 视图:list[SessionEvent](已折叠);惰性重建
|
|
63
|
+
self._surface = []
|
|
64
|
+
self._surface_dirty = True
|
|
65
|
+
if self.store_path:
|
|
66
|
+
self._load()
|
|
67
|
+
|
|
68
|
+
# ---- 写入 ----
|
|
69
|
+
|
|
70
|
+
def append(self, ev_type, data, *, surface_op=None, turn_id=None):
|
|
71
|
+
"""追加事件。surface_op:
|
|
72
|
+
None -> 追加进 surface(若是 surface 类型)
|
|
73
|
+
"shadow" -> 仅记录,不进 surface(审计/中间产物)
|
|
74
|
+
{"op":"replace","start_seq":N,"end_seq":M} -> 把 [N,M] 折叠为本条
|
|
75
|
+
"""
|
|
76
|
+
with self._lock:
|
|
77
|
+
self._seq += 1
|
|
78
|
+
ev = SessionEvent(self._seq, ev_type, data,
|
|
79
|
+
surface_op=surface_op, turn_id=turn_id)
|
|
80
|
+
self._events.append(ev)
|
|
81
|
+
if isinstance(surface_op, dict) and surface_op.get("op") == "replace":
|
|
82
|
+
self._replace_generation += 1
|
|
83
|
+
self._surface_dirty = True
|
|
84
|
+
self._persist_one(ev)
|
|
85
|
+
return ev
|
|
86
|
+
|
|
87
|
+
# ---- 派生 ----
|
|
88
|
+
|
|
89
|
+
def _fold(self):
|
|
90
|
+
"""按事件序重放 surface。replace 折叠 [start_seq, end_seq] 区间为该条摘要。"""
|
|
91
|
+
surface = [] # list[SessionEvent]
|
|
92
|
+
for ev in self._events:
|
|
93
|
+
op = ev.surface_op
|
|
94
|
+
if op == "shadow":
|
|
95
|
+
continue
|
|
96
|
+
if isinstance(op, dict) and op.get("op") == "replace":
|
|
97
|
+
start, end = op["start_seq"], op["end_seq"]
|
|
98
|
+
surface = [e for e in surface if not (start <= e.seq <= end)]
|
|
99
|
+
surface.append(ev)
|
|
100
|
+
continue
|
|
101
|
+
if ev.type in _SURFACE_TYPES:
|
|
102
|
+
surface.append(ev)
|
|
103
|
+
return surface
|
|
104
|
+
|
|
105
|
+
def derive_messages(self):
|
|
106
|
+
"""模型输入消息列表 [{role, content}]。system 节点去重合并(保留最后一条同 role)。"""
|
|
107
|
+
with self._lock:
|
|
108
|
+
if self._surface_dirty:
|
|
109
|
+
self._surface = self._fold()
|
|
110
|
+
self._surface_dirty = False
|
|
111
|
+
snap = list(self._surface)
|
|
112
|
+
msgs = []
|
|
113
|
+
for ev in snap:
|
|
114
|
+
role = "system" if ev.type == "system_message" else (
|
|
115
|
+
"assistant" if ev.type == "assistant_message" else "user")
|
|
116
|
+
msgs.append({"role": role, "content": str(ev.data.get("content", "")),
|
|
117
|
+
"seq": ev.seq})
|
|
118
|
+
# 同 role 连续合并(CLI 提示词偏好单条 system/user)
|
|
119
|
+
merged = []
|
|
120
|
+
for m in msgs:
|
|
121
|
+
if merged and merged[-1]["role"] == m["role"]:
|
|
122
|
+
merged[-1]["content"] += "\n\n" + m["content"]
|
|
123
|
+
merged[-1]["seq"] = m["seq"]
|
|
124
|
+
else:
|
|
125
|
+
merged.append(dict(m))
|
|
126
|
+
return merged
|
|
127
|
+
|
|
128
|
+
def replace_generation(self):
|
|
129
|
+
with self._lock:
|
|
130
|
+
return self._replace_generation
|
|
131
|
+
|
|
132
|
+
def events(self):
|
|
133
|
+
with self._lock:
|
|
134
|
+
return list(self._events)
|
|
135
|
+
|
|
136
|
+
# ---- 持久化 ----
|
|
137
|
+
|
|
138
|
+
def _persist_one(self, ev):
|
|
139
|
+
if not self.store_path:
|
|
140
|
+
return
|
|
141
|
+
with open(self.store_path, "a", encoding="utf-8") as f:
|
|
142
|
+
f.write(json.dumps(ev.to_dict(), ensure_ascii=False) + "\n")
|
|
143
|
+
|
|
144
|
+
def _load(self):
|
|
145
|
+
"""启动恢复:逐行读 JSONL,坏行跳过(lazy skip)。"""
|
|
146
|
+
try:
|
|
147
|
+
with open(self.store_path, encoding="utf-8") as f:
|
|
148
|
+
for line in f:
|
|
149
|
+
line = line.strip()
|
|
150
|
+
if not line.startswith("{"):
|
|
151
|
+
continue
|
|
152
|
+
try:
|
|
153
|
+
self._events.append(SessionEvent.from_dict(json.loads(line)))
|
|
154
|
+
except Exception:
|
|
155
|
+
continue
|
|
156
|
+
if self._events:
|
|
157
|
+
self._seq = max(e.seq for e in self._events)
|
|
158
|
+
self._replace_generation = sum(
|
|
159
|
+
1 for e in self._events
|
|
160
|
+
if isinstance(e.surface_op, dict) and e.surface_op.get("op") == "replace")
|
|
161
|
+
except OSError:
|
|
162
162
|
pass
|