webbee 0.3.7__tar.gz → 0.3.8__tar.gz
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.
- {webbee-0.3.7 → webbee-0.3.8}/CHANGELOG.md +10 -0
- {webbee-0.3.7 → webbee-0.3.8}/PKG-INFO +1 -1
- {webbee-0.3.7 → webbee-0.3.8}/pyproject.toml +1 -1
- webbee-0.3.8/src/webbee/__init__.py +1 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/repl.py +8 -4
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/steer.py +7 -1
- webbee-0.3.8/src/webbee/tokens.py +39 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/tui.py +17 -7
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_repl.py +8 -2
- webbee-0.3.8/tests/test_tokens.py +57 -0
- webbee-0.3.7/src/webbee/__init__.py +0 -1
- {webbee-0.3.7 → webbee-0.3.8}/.github/workflows/publish.yml +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/.gitignore +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/LICENSE +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/README.md +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/install.sh +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/account.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/banner_art.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/checkpoints.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/cli.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/clipboard.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/commands.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/config.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/details.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/events.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/frames.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/intel/__init__.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/intel/chunker.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/intel/embed.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/intel/graph.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/intel/indexer.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/intel/models.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/intel/query.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/intel/service.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/intel/store.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/intel/vectors.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/intel/watch.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/output_pane.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/remote.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/render.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/repo.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/session.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/sessions.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/stream.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/thread.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/tools.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/src/webbee/update.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/__init__.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_account.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_checkpoints.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_cli.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_clipboard.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_commands.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_config.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_cpc_contract_stable.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_details.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_events.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_freeze_fix.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_intel_chunker.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_intel_embed.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_intel_graph.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_intel_indexer.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_intel_no_numpy.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_intel_query.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_intel_service.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_intel_store.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_intel_vectors.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_intel_watch.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_marathon.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_packaging.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_render.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_repo.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_session.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_sessions.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_steer.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_stream.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_thread.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_tools.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_tui.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_tui_hardening.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_update.py +0 -0
- {webbee-0.3.7 → webbee-0.3.8}/tests/test_version.py +0 -0
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.8
|
|
4
|
+
|
|
5
|
+
- No more sign-in races: token refresh is serialized and retries once after
|
|
6
|
+
a sibling terminal rotates the session — multiple open terminals no longer
|
|
7
|
+
knock each other out with "session expired".
|
|
8
|
+
- No more dock lockup: a turn that fails (for example, an expired session)
|
|
9
|
+
now clears the "working" state, and every key keeps responding even if the
|
|
10
|
+
turn state ever goes stale. The idle pickup poller also backs off instead
|
|
11
|
+
of hammering a signed-out session.
|
|
12
|
+
|
|
3
13
|
## 0.3.7
|
|
4
14
|
|
|
5
15
|
- Terminal liveness: a consent answered from Telegram/panel no longer
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.8"
|
|
@@ -58,8 +58,8 @@ async def run_marathon(cfg, mode: str, goal: str, *, sink=None, auth=None,
|
|
|
58
58
|
|
|
59
59
|
workspace = os.getcwd()
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
from webbee.tokens import make_token_provider
|
|
62
|
+
token_provider = make_token_provider(cfg, auth)
|
|
63
63
|
|
|
64
64
|
if agent_factory is None:
|
|
65
65
|
agent_factory = lambda c, tp, ws, m: AgentSession(c, tp, ws, m) # noqa: E731
|
|
@@ -72,9 +72,11 @@ async def run_marathon(cfg, mode: str, goal: str, *, sink=None, auth=None,
|
|
|
72
72
|
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
73
73
|
await agent.stop()
|
|
74
74
|
sink.note("Interrupted.")
|
|
75
|
+
sink.end_turn("") # clear busy (poller starvation guard)
|
|
75
76
|
return ""
|
|
76
77
|
except Exception as e: # network/auth/etc — never crash
|
|
77
78
|
sink.note(f"Error: {type(e).__name__}: {e}")
|
|
79
|
+
sink.end_turn("") # clear busy: a stuck 'working' also starves the idle-steer poller
|
|
78
80
|
return ""
|
|
79
81
|
sink.end_turn(text)
|
|
80
82
|
return text
|
|
@@ -122,8 +124,8 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
|
|
|
122
124
|
|
|
123
125
|
workspace = os.getcwd()
|
|
124
126
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
from webbee.tokens import make_token_provider
|
|
128
|
+
token_provider = make_token_provider(cfg, auth)
|
|
127
129
|
|
|
128
130
|
# Prod dock path = the default reader + a real tty + no injected sink; tests
|
|
129
131
|
# inject sink/read_line and take the plain fallback loop.
|
|
@@ -287,9 +289,11 @@ async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None,
|
|
|
287
289
|
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
288
290
|
_sink.abort()
|
|
289
291
|
_sink.note("Interrupted.")
|
|
292
|
+
_sink.end_turn("") # clear busy (poller starvation guard)
|
|
290
293
|
return
|
|
291
294
|
except Exception as e: # network/auth/etc — never crash the REPL
|
|
292
295
|
_sink.note(f"Error: {type(e).__name__}: {e}")
|
|
296
|
+
_sink.end_turn("") # clear busy: a stuck 'working' also starves the idle-steer poller
|
|
293
297
|
return
|
|
294
298
|
_sink.end_turn(text)
|
|
295
299
|
|
|
@@ -73,8 +73,10 @@ async def poll_idle_steer(cfg, token_provider, *, workspace: str, is_busy,
|
|
|
73
73
|
from webbee.thread import fetch_pending_steer
|
|
74
74
|
derived = ""
|
|
75
75
|
backlog: deque = deque()
|
|
76
|
+
failures = 0 # consecutive fetch/auth failures -> backoff (a logged-out
|
|
77
|
+
# terminal must not hammer the token-refresh path every 4s)
|
|
76
78
|
while True:
|
|
77
|
-
await asyncio.sleep(interval)
|
|
79
|
+
await asyncio.sleep(min(interval * (2 ** min(failures, 4)), 60.0))
|
|
78
80
|
try:
|
|
79
81
|
if is_busy():
|
|
80
82
|
continue
|
|
@@ -103,4 +105,8 @@ async def poll_idle_steer(cfg, token_provider, *, workspace: str, is_busy,
|
|
|
103
105
|
except Exception:
|
|
104
106
|
# Fail-soft by design: a network blip / auth hiccup skips this
|
|
105
107
|
# tick; undrained items stay durable on the gateway (1h TTL).
|
|
108
|
+
# Consecutive failures back the poll off (up to 60s) so a
|
|
109
|
+
# logged-out terminal never hammers the token-refresh path.
|
|
110
|
+
failures += 1
|
|
106
111
|
continue
|
|
112
|
+
failures = 0
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Serialized token acquisition for every webbee gateway call.
|
|
2
|
+
|
|
3
|
+
The gateway rotates refresh tokens SINGLE-USE (an atomic claim revokes the
|
|
4
|
+
presented token and mints a new pair) and the SDK's ensure_access_token has
|
|
5
|
+
no lock: two concurrent refreshes at an access-token expiry boundary — the
|
|
6
|
+
idle-steer poller vs. a starting turn, or two open terminals sharing the
|
|
7
|
+
same on-disk creds — race, and the loser's refresh 401s as "session
|
|
8
|
+
expired" (Valentin, live 2026-07-15, right after 0.3.7 made the poller a
|
|
9
|
+
frequent caller).
|
|
10
|
+
|
|
11
|
+
Two-layer guard:
|
|
12
|
+
* an asyncio.Lock serializes ALL in-process acquisitions (poller / boot
|
|
13
|
+
replay / turns never refresh concurrently);
|
|
14
|
+
* on failure, ONE short-delay retry — ensure_access_token re-reads the
|
|
15
|
+
creds file, so when a SIBLING terminal won the race and saved the
|
|
16
|
+
rotated pair, the retry succeeds from its fresh refresh token. A real
|
|
17
|
+
logged-out state still fails (both attempts), just ~0.6s later.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
|
|
23
|
+
_RETRY_DELAY_S = 0.6
|
|
24
|
+
|
|
25
|
+
_lock = asyncio.Lock()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def make_token_provider(cfg, auth):
|
|
29
|
+
"""The ONE token_provider factory the REPL/marathon paths share."""
|
|
30
|
+
|
|
31
|
+
async def token_provider() -> str:
|
|
32
|
+
async with _lock:
|
|
33
|
+
try:
|
|
34
|
+
return await auth.ensure_access_token(cfg)
|
|
35
|
+
except Exception:
|
|
36
|
+
await asyncio.sleep(_RETRY_DELAY_S)
|
|
37
|
+
return await auth.ensure_access_token(cfg)
|
|
38
|
+
|
|
39
|
+
return token_provider
|
|
@@ -157,6 +157,16 @@ async def run_session(*, pane, on_line, mode_getter, on_cycle, status,
|
|
|
157
157
|
|
|
158
158
|
kb = KeyBindings()
|
|
159
159
|
|
|
160
|
+
def _busy_live() -> bool:
|
|
161
|
+
"""Lockout-proof busy for the key handlers: busy only while the turn
|
|
162
|
+
TASK is genuinely alive. A turn that died without clearing the sink's
|
|
163
|
+
busy flag (an error path that skipped end_turn) must never brick the
|
|
164
|
+
dock -- Enter/Esc/Ctrl-C/Ctrl-D all gate on THIS, so a stale flag
|
|
165
|
+
degrades to a cosmetic toolbar glitch instead of an unusable input
|
|
166
|
+
(Valentin, live 2026-07-15: 'working' spun and NO key reacted)."""
|
|
167
|
+
t = turn.get("task")
|
|
168
|
+
return bool(is_busy() and t is not None and not t.done())
|
|
169
|
+
|
|
160
170
|
@kb.add("enter")
|
|
161
171
|
def _enter(event):
|
|
162
172
|
text = scrub_mouse_residue(buf.text) # never send leaked mouse reports
|
|
@@ -164,11 +174,11 @@ async def run_session(*, pane, on_line, mode_getter, on_cycle, status,
|
|
|
164
174
|
if consent_pending():
|
|
165
175
|
resolve_consent(text) # ICNLI: relay the raw reply verbatim
|
|
166
176
|
return
|
|
167
|
-
if not text.strip() and sel["i"] is not None and steps_nav and not
|
|
177
|
+
if not text.strip() and sel["i"] is not None and steps_nav and not _busy_live():
|
|
168
178
|
idx, sel["i"] = sel["i"], None
|
|
169
179
|
event.app.create_background_task(steps_nav["expand"](idx))
|
|
170
180
|
return
|
|
171
|
-
if
|
|
181
|
+
if _busy_live() or not text.strip():
|
|
172
182
|
return
|
|
173
183
|
turn["task"] = event.app.create_background_task(_run_turn(text))
|
|
174
184
|
|
|
@@ -179,11 +189,11 @@ async def run_session(*, pane, on_line, mode_getter, on_cycle, status,
|
|
|
179
189
|
|
|
180
190
|
@kb.add("c-c")
|
|
181
191
|
def _interrupt(event):
|
|
182
|
-
_interrupt_action(turn,
|
|
192
|
+
_interrupt_action(turn, _busy_live, stop_turn, event)
|
|
183
193
|
|
|
184
194
|
@kb.add("c-d")
|
|
185
195
|
def _eof(event):
|
|
186
|
-
if not
|
|
196
|
+
if not _busy_live():
|
|
187
197
|
event.app.exit()
|
|
188
198
|
|
|
189
199
|
sel = {"i": None} # None = no selection; else 0-based step index
|
|
@@ -197,20 +207,20 @@ async def run_session(*, pane, on_line, mode_getter, on_cycle, status,
|
|
|
197
207
|
@kb.add("up")
|
|
198
208
|
def _step_up(event):
|
|
199
209
|
n = _nav_count()
|
|
200
|
-
if n and not buf.text and not
|
|
210
|
+
if n and not buf.text and not _busy_live():
|
|
201
211
|
sel["i"] = (n - 1) if sel["i"] is None else max(0, sel["i"] - 1)
|
|
202
212
|
event.app.invalidate()
|
|
203
213
|
|
|
204
214
|
@kb.add("down")
|
|
205
215
|
def _step_down(event):
|
|
206
216
|
n = _nav_count()
|
|
207
|
-
if n and not buf.text and not
|
|
217
|
+
if n and not buf.text and not _busy_live():
|
|
208
218
|
sel["i"] = 0 if sel["i"] is None else min(n - 1, sel["i"] + 1)
|
|
209
219
|
event.app.invalidate()
|
|
210
220
|
|
|
211
221
|
@kb.add("escape")
|
|
212
222
|
def _step_clear(event):
|
|
213
|
-
_escape_action(sel, turn,
|
|
223
|
+
_escape_action(sel, turn, _busy_live, stop_turn, event, buf=buf)
|
|
214
224
|
|
|
215
225
|
@kb.add("pageup")
|
|
216
226
|
def _pgup(event):
|
|
@@ -162,7 +162,10 @@ def test_agent_error_is_swallowed_and_loop_continues():
|
|
|
162
162
|
assert agent.tasks == ["do it"]
|
|
163
163
|
assert any("Error" in n for n in sink.notes)
|
|
164
164
|
assert not any(NO_CYRILLIC.search(n) for n in sink.notes)
|
|
165
|
-
|
|
165
|
+
# Liveness: this path MUST clear busy via end_turn("") -- a stuck busy
|
|
166
|
+
# flag locked the whole dock out (live 2026-07-15) and starves the idle-
|
|
167
|
+
# steer poller. Empty text = no final panel, just the state reset.
|
|
168
|
+
assert sink.turns == [""]
|
|
166
169
|
|
|
167
170
|
|
|
168
171
|
def test_login_command_calls_auth_and_logs_in():
|
|
@@ -234,7 +237,10 @@ def test_ctrl_c_mid_turn_aborts_and_returns_to_prompt():
|
|
|
234
237
|
assert sink.aborted is True
|
|
235
238
|
assert any("Interrupted" in n for n in sink.notes)
|
|
236
239
|
assert not any(NO_CYRILLIC.search(n) for n in sink.notes)
|
|
237
|
-
|
|
240
|
+
# Liveness: this path MUST clear busy via end_turn("") -- a stuck busy
|
|
241
|
+
# flag locked the whole dock out (live 2026-07-15) and starves the idle-
|
|
242
|
+
# steer poller. Empty text = no final panel, just the state reset.
|
|
243
|
+
assert sink.turns == [""]
|
|
238
244
|
|
|
239
245
|
|
|
240
246
|
def test_welcome_shown_on_start():
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Serialized token provider (webbee.tokens) — the anti-lockout auth guard."""
|
|
2
|
+
import asyncio
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from webbee.tokens import make_token_provider
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _Auth:
|
|
10
|
+
def __init__(self, fail_first: int = 0):
|
|
11
|
+
self.calls = 0
|
|
12
|
+
self.concurrent = 0
|
|
13
|
+
self.max_concurrent = 0
|
|
14
|
+
self.fail_first = fail_first
|
|
15
|
+
|
|
16
|
+
async def ensure_access_token(self, cfg):
|
|
17
|
+
self.calls += 1
|
|
18
|
+
self.concurrent += 1
|
|
19
|
+
self.max_concurrent = max(self.max_concurrent, self.concurrent)
|
|
20
|
+
try:
|
|
21
|
+
await asyncio.sleep(0.01)
|
|
22
|
+
if self.calls <= self.fail_first:
|
|
23
|
+
raise RuntimeError("invalid refresh token")
|
|
24
|
+
return f"tok{self.calls}"
|
|
25
|
+
finally:
|
|
26
|
+
self.concurrent -= 1
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_concurrent_acquisitions_serialized():
|
|
30
|
+
auth = _Auth()
|
|
31
|
+
tp = make_token_provider(object(), auth)
|
|
32
|
+
|
|
33
|
+
async def go():
|
|
34
|
+
return await asyncio.gather(tp(), tp(), tp())
|
|
35
|
+
|
|
36
|
+
toks = asyncio.run(go())
|
|
37
|
+
assert all(t.startswith("tok") for t in toks)
|
|
38
|
+
assert auth.max_concurrent == 1 # the lock: never two refreshes at once
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_failure_retries_once_after_sibling_rotation(monkeypatch):
|
|
42
|
+
import webbee.tokens as wt
|
|
43
|
+
monkeypatch.setattr(wt, "_RETRY_DELAY_S", 0)
|
|
44
|
+
auth = _Auth(fail_first=1) # first attempt loses the rotation race
|
|
45
|
+
tp = make_token_provider(object(), auth)
|
|
46
|
+
assert asyncio.run(tp()) == "tok2" # retry re-reads (sibling saved) and wins
|
|
47
|
+
assert auth.calls == 2
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_true_logged_out_still_fails(monkeypatch):
|
|
51
|
+
import webbee.tokens as wt
|
|
52
|
+
monkeypatch.setattr(wt, "_RETRY_DELAY_S", 0)
|
|
53
|
+
auth = _Auth(fail_first=99)
|
|
54
|
+
tp = make_token_provider(object(), auth)
|
|
55
|
+
with pytest.raises(RuntimeError):
|
|
56
|
+
asyncio.run(tp())
|
|
57
|
+
assert auth.calls == 2 # exactly one retry, no hammering
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.7"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|