meshcode 2.11.133__tar.gz → 2.11.135__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.
- {meshcode-2.11.133 → meshcode-2.11.135}/PKG-INFO +1 -1
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/__init__.py +1 -1
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/hostd.py +0 -146
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/meshcode_mcp/backend.py +29 -8
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/meshcode_mcp/server.py +93 -5
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/rpc_allowlist.py +1 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode.egg-info/SOURCES.txt +1 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/pyproject.toml +1 -1
- meshcode-2.11.135/tests/test_task_progress.py +147 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/README.md +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/__main__.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/_session_handoff_template.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/cli.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/compat.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/daemon.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/doctor.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/hooks/__init__.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/hooks/repo_path_lock.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/invites.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/launcher.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/meshcode_mcp/swarm.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/meshcode_mcp/test_swarm.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/preferences.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/protocol_handler.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/run_agent.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/secrets.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/self_update.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/up.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode/upload.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/setup.cfg +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_autonomous_prompt_inject.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_core.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_doctor.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_hostd_zombie_sessions.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_live_mesh_guard.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_session_replay_gate.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_stop_ghost_terminal.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_swarm_events.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_terminal_lifecycle.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.135}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -1037,26 +1037,6 @@ def _recycle_blocked(st, target, now=None):
|
|
|
1037
1037
|
return None
|
|
1038
1038
|
|
|
1039
1039
|
|
|
1040
|
-
# Samuel rule 2026-06-04: never recycle a CONNECTED agent (live MCP session) except
|
|
1041
|
-
# the >3h uptime lifecycle. BUSY_STATUSES (working/online/busy) MISSES a connected-
|
|
1042
|
-
# but-idle agent (status idle/standby, window open, heartbeat fresh) — a fresh
|
|
1043
|
-
# heartbeat is the stronger 'live session' signal, so an idle-but-connected agent
|
|
1044
|
-
# was being version-recycled out from under the user (the storm he kept seeing).
|
|
1045
|
-
_CONNECTED_HEARTBEAT_S = _env_int("MESHCODE_CONNECTED_HEARTBEAT_SEC", 60, 10)
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
def _agent_connected(a) -> bool:
|
|
1049
|
-
"""True if the agent has a live MCP session: an explicitly-busy status OR a
|
|
1050
|
-
very-recent heartbeat (window open even when idle/standby)."""
|
|
1051
|
-
if (a.get("status") or "") in BUSY_STATUSES:
|
|
1052
|
-
return True
|
|
1053
|
-
hb = a.get("heartbeat_age_s")
|
|
1054
|
-
try:
|
|
1055
|
-
return hb is not None and float(hb) < _CONNECTED_HEARTBEAT_S
|
|
1056
|
-
except (TypeError, ValueError):
|
|
1057
|
-
return False
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
1040
|
# Part 2 (Samuel req #2 2026-06-04): on a VISIBLE recycle, close the OLD window so
|
|
1061
1041
|
# old+new don't both stay open (audit gap 6a203baa). DRY-RUN first (commander q2 +
|
|
1062
1042
|
# reaper safe-arm pattern): log-only until the logs confirm it's ONLY the old pid.
|
|
@@ -2122,57 +2102,6 @@ def _do_recycles(api_key: str, host_id: str) -> int:
|
|
|
2122
2102
|
# it stays dead even if a schedule row reappears. Crash-RESPAWN (_do_respawns) is
|
|
2123
2103
|
# UNAFFECTED — only RECYCLE triggers are killed.
|
|
2124
2104
|
return 0
|
|
2125
|
-
cfg = _rpc("mc_host_config_get", {"p_api_key": api_key, "p_host_id": host_id})
|
|
2126
|
-
if not cfg or not cfg.get("ok"):
|
|
2127
|
-
return 0
|
|
2128
|
-
pol = _rpc("mc_host_recycle_policy", {"p_api_key": api_key, "p_host_id": host_id})
|
|
2129
|
-
mode = (pol or {}).get("recycle_mode")
|
|
2130
|
-
value = (pol or {}).get("recycle_value")
|
|
2131
|
-
if mode != "time" or not value:
|
|
2132
|
-
return 0 # context-recycle is agent-cooperative; only time is daemon-driven
|
|
2133
|
-
st = _load_state()
|
|
2134
|
-
now = time.time()
|
|
2135
|
-
n = 0
|
|
2136
|
-
seen = set()
|
|
2137
|
-
for a in cfg.get("agents", []):
|
|
2138
|
-
if a.get("desired_state") != "running":
|
|
2139
|
-
continue
|
|
2140
|
-
proj, agent = a.get("project_name"), a.get("name")
|
|
2141
|
-
if not proj or not agent:
|
|
2142
|
-
continue
|
|
2143
|
-
key = f"{a.get('project_id')}/{agent}"
|
|
2144
|
-
seen.add(key)
|
|
2145
|
-
first = st.get(key)
|
|
2146
|
-
if first is None:
|
|
2147
|
-
st[key] = now # start the uptime clock on first observation
|
|
2148
|
-
continue
|
|
2149
|
-
if (a.get("status") or "") in BUSY_STATUSES:
|
|
2150
|
-
continue # task boundary only — never mid-task
|
|
2151
|
-
if (now - first) >= float(value) * 3600.0:
|
|
2152
|
-
_log(f"RECYCLE {proj}/{agent} (uptime {(now-first)/3600:.1f}h >= {value}h)")
|
|
2153
|
-
# Server-authorized clean-exit (task 548c863e, mig 364): SIGNAL the
|
|
2154
|
-
# recycle instead of spawning. A direct _spawn_agent here duplicates
|
|
2155
|
-
# the still-alive process. mc_request_recycle sets a flag; the
|
|
2156
|
-
# agent's wait-loop consumes it, returns must_exit/reason=recycle,
|
|
2157
|
-
# exits CLEAN (Stop-hook writes the handoff), then the respawn path
|
|
2158
|
-
# (_do_respawns) relaunches it fresh. Recorded via mc_record_recycle
|
|
2159
|
-
# so it's NEVER counted against the crash respawn cap.
|
|
2160
|
-
req = _rpc("mc_request_recycle",
|
|
2161
|
-
{"p_api_key": api_key, "p_project_id": a.get("project_id"), "p_agent_name": agent})
|
|
2162
|
-
if req and req.get("ok") and req.get("requested"):
|
|
2163
|
-
_rpc("mc_record_recycle",
|
|
2164
|
-
{"p_api_key": api_key, "p_project_id": a.get("project_id"), "p_agent_name": agent})
|
|
2165
|
-
st[key] = now # reset clock after signaling recycle
|
|
2166
|
-
n += 1
|
|
2167
|
-
elif req and req.get("ok") and not req.get("requested"):
|
|
2168
|
-
# Agent flipped busy between the roster read and the request —
|
|
2169
|
-
# skip; retry next sweep at the next task boundary.
|
|
2170
|
-
_log(f"SKIP recycle {proj}/{agent}: {req.get('reason','not_requested')}")
|
|
2171
|
-
# prune state for agents no longer managed on this host
|
|
2172
|
-
for k in [k for k in st if k not in seen]:
|
|
2173
|
-
st.pop(k, None)
|
|
2174
|
-
_save_state(st)
|
|
2175
|
-
return n
|
|
2176
2105
|
|
|
2177
2106
|
|
|
2178
2107
|
# ------------------------------------------------------------------
|
|
@@ -2502,10 +2431,6 @@ def _hostd_bootstrap(verbose: bool = False) -> bool:
|
|
|
2502
2431
|
return False
|
|
2503
2432
|
|
|
2504
2433
|
|
|
2505
|
-
# a69ac010-C: after this many version-recycles at the SAME cv with no advance, STOP (env won't converge).
|
|
2506
|
-
_VERREC_MAX_ATTEMPTS = 3
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
2434
|
def _do_version_recycles(api_key: str, host_id: str) -> int:
|
|
2510
2435
|
"""Recycle managed agents running an OLDER meshcode than what's installed on disk so they pick up
|
|
2511
2436
|
the auto-updated version (task 14782bb4 D). GRACIOUS (guardrail #5): idle-only (NEVER a BUSY agent /
|
|
@@ -2516,77 +2441,6 @@ def _do_version_recycles(api_key: str, host_id: str) -> int:
|
|
|
2516
2441
|
# This was the env-mismatch storm source; fixed-at-source via run_agent env-sync, but the
|
|
2517
2442
|
# whole recycle feature is being removed per owner. Crash-RESPAWN is unaffected.
|
|
2518
2443
|
return 0
|
|
2519
|
-
cfg = _rpc("mc_host_config_get", {"p_api_key": api_key, "p_host_id": host_id})
|
|
2520
|
-
if not cfg or not cfg.get("ok"):
|
|
2521
|
-
return 0
|
|
2522
|
-
try:
|
|
2523
|
-
from meshcode import self_update as _su
|
|
2524
|
-
disk = _su._current_version()
|
|
2525
|
-
except Exception:
|
|
2526
|
-
return 0
|
|
2527
|
-
if not disk:
|
|
2528
|
-
return 0
|
|
2529
|
-
st = _load_state()
|
|
2530
|
-
now = time.time()
|
|
2531
|
-
n = 0
|
|
2532
|
-
for a in cfg.get("agents", []):
|
|
2533
|
-
if a.get("desired_state") != "running":
|
|
2534
|
-
continue
|
|
2535
|
-
cv = a.get("cli_version")
|
|
2536
|
-
if not cv:
|
|
2537
|
-
continue
|
|
2538
|
-
try:
|
|
2539
|
-
if not _su._is_newer(disk, cv):
|
|
2540
|
-
continue # agent already on the on-disk version (or newer) — nothing to do
|
|
2541
|
-
except Exception:
|
|
2542
|
-
continue
|
|
2543
|
-
if _agent_connected(a):
|
|
2544
|
-
continue # Samuel rule: never version-recycle a CONNECTED agent (live MCP session,
|
|
2545
|
-
# even if idle/standby) — the >3h uptime lifecycle (_do_recycles) is the
|
|
2546
|
-
# only recycle that may touch a connected agent.
|
|
2547
|
-
proj, agent = a.get("project_name"), a.get("name")
|
|
2548
|
-
if not proj or not agent:
|
|
2549
|
-
continue
|
|
2550
|
-
if _recycle_blocked(st, f"{proj}/{agent}", now):
|
|
2551
|
-
continue # 451d33a0 unify: already blocked as a non-converging recycle — don't also version-recycle it
|
|
2552
|
-
key = f"verrec/{a.get('project_id')}/{agent}"
|
|
2553
|
-
if now - float(st.get(key, 0) or 0) < 1800:
|
|
2554
|
-
continue # rate-limit: <=1 version-recycle per agent / 30min (no recycle-storm)
|
|
2555
|
-
# a69ac010-C CONVERGENCE GUARD: if we've already version-recycled this agent _VERREC_MAX_ATTEMPTS
|
|
2556
|
-
# times at THIS exact cv and it never advanced, the agent's spawn env is OLDER than hostd's disk
|
|
2557
|
-
# and recycling will NEVER converge (respawn pulls the same stale env -> same cv -> recycle again).
|
|
2558
|
-
# STOP + alert ONCE instead of version-recycling every 30min forever (the env-mismatch storm RC).
|
|
2559
|
-
conv_key = f"verrec_conv/{a.get('project_id')}/{agent}"
|
|
2560
|
-
conv = dict(st.get(conv_key) or {})
|
|
2561
|
-
if conv.get("cv") == cv and int(conv.get("count", 0)) >= _VERREC_MAX_ATTEMPTS:
|
|
2562
|
-
if not conv.get("alerted"):
|
|
2563
|
-
conv["alerted"] = True
|
|
2564
|
-
st[conv_key] = conv
|
|
2565
|
-
_save_state(st)
|
|
2566
|
-
_log(f"VERSION-RECYCLE STUCK {proj}/{agent}: still {cv} after {conv['count']} recycles "
|
|
2567
|
-
f"(disk {disk}); spawn env older than hostd — align it (e.g. "
|
|
2568
|
-
f"~/meshcode-env/bin/pip install -U meshcode). BLOCKING further version-recycles "
|
|
2569
|
-
f"until cv advances. [recycle_blocked_reason=version_no_converge]")
|
|
2570
|
-
continue
|
|
2571
|
-
req = _rpc("mc_request_recycle",
|
|
2572
|
-
{"p_api_key": api_key, "p_project_id": a.get("project_id"), "p_agent_name": agent})
|
|
2573
|
-
if req and req.get("ok") and req.get("requested"):
|
|
2574
|
-
_rpc("mc_record_recycle",
|
|
2575
|
-
{"p_api_key": api_key, "p_project_id": a.get("project_id"), "p_agent_name": agent})
|
|
2576
|
-
st[key] = now
|
|
2577
|
-
# advance convergence counter for THIS cv; a cv change (env fixed) resets + releases the guard.
|
|
2578
|
-
if conv.get("cv") == cv:
|
|
2579
|
-
conv["count"] = int(conv.get("count", 0)) + 1
|
|
2580
|
-
else:
|
|
2581
|
-
conv = {"cv": cv, "count": 1}
|
|
2582
|
-
conv["alerted"] = False
|
|
2583
|
-
st[conv_key] = conv
|
|
2584
|
-
n += 1
|
|
2585
|
-
_log(f"VERSION-RECYCLE {proj}/{agent}: running {cv} < disk {disk} — recycle at idle to update "
|
|
2586
|
-
f"(attempt {conv['count']}/{_VERREC_MAX_ATTEMPTS})")
|
|
2587
|
-
if n:
|
|
2588
|
-
_save_state(st)
|
|
2589
|
-
return n
|
|
2590
2444
|
|
|
2591
2445
|
|
|
2592
2446
|
# c301be69: hold the singleton lock handle for the daemon's lifetime — module
|
|
@@ -907,16 +907,21 @@ def take_memory_hints() -> List[Dict]:
|
|
|
907
907
|
return out
|
|
908
908
|
|
|
909
909
|
|
|
910
|
-
def read_inbox(project_id: str, agent: str, mark_read: bool = True, api_key: Optional[str] = None
|
|
911
|
-
|
|
910
|
+
def read_inbox(project_id: str, agent: str, mark_read: bool = True, api_key: Optional[str] = None,
|
|
911
|
+
rpc_result: Optional[Dict] = None) -> List[Dict]:
|
|
912
|
+
# Use SECURITY DEFINER RPC when api_key is available (bypasses RLS safely).
|
|
913
|
+
# rpc_result (mig 521 wait-drain): a pre-fetched mc_read_inbox payload (the
|
|
914
|
+
# 'inbox' field of mc_wait_poll v5). When given, skip the RPC and run the
|
|
915
|
+
# identical post-process (decrypt, memory_hints, ack) on it.
|
|
912
916
|
global _LAST_MEMORY_HINTS
|
|
913
917
|
if api_key:
|
|
914
|
-
rpc_result
|
|
915
|
-
"
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
918
|
+
if rpc_result is None:
|
|
919
|
+
rpc_result = sb_rpc("mc_read_inbox", {
|
|
920
|
+
"p_api_key": api_key,
|
|
921
|
+
"p_project_id": project_id,
|
|
922
|
+
"p_agent_name": agent,
|
|
923
|
+
"p_mark_read": mark_read,
|
|
924
|
+
})
|
|
920
925
|
if isinstance(rpc_result, dict) and rpc_result.get("ok"):
|
|
921
926
|
# mig 220: stash memory_hints so the calling tool can attach
|
|
922
927
|
# them to its response. Reset on every call so stale hints
|
|
@@ -1389,6 +1394,22 @@ def task_complete(api_key, project_id, task_id, completing_agent, summary=""):
|
|
|
1389
1394
|
})
|
|
1390
1395
|
|
|
1391
1396
|
|
|
1397
|
+
def task_progress(api_key, project_id, task_id, pct, note=None):
|
|
1398
|
+
"""Report real progress pct (0-100) on a claimed/in_progress/in_review task.
|
|
1399
|
+
|
|
1400
|
+
Server-side: public.mc_task_progress (mig 516) — claim-holder or owner;
|
|
1401
|
+
updates progress_pct/note/last_progress_at, extends the claim lease 4h,
|
|
1402
|
+
appends to deliverables.progress_log. The dashboard pbar reads this as
|
|
1403
|
+
progress_source='reported' (task 6b2afa5d: pbar accuracy)."""
|
|
1404
|
+
return sb_rpc("mc_task_progress", {
|
|
1405
|
+
"p_api_key": api_key,
|
|
1406
|
+
"p_project_id": project_id,
|
|
1407
|
+
"p_task_id": task_id,
|
|
1408
|
+
"p_pct": pct,
|
|
1409
|
+
"p_note": note,
|
|
1410
|
+
})
|
|
1411
|
+
|
|
1412
|
+
|
|
1392
1413
|
def feature_flag_enabled(api_key, flag_name):
|
|
1393
1414
|
"""Agent-facing feature-flag check (mig478). The FE's mc_check_feature_flag
|
|
1394
1415
|
is auth.uid()-only, so agents use this api-key variant. Returns the raw
|
|
@@ -3665,6 +3665,11 @@ def _wait_poll_or_legacy() -> Dict[str, Any]:
|
|
|
3665
3665
|
"p_api_key": api_key,
|
|
3666
3666
|
"p_project_id": _PROJECT_ID,
|
|
3667
3667
|
"p_agent_name": AGENT_NAME,
|
|
3668
|
+
# wait-loop fase 2 (proposal 0bd63160, mig 521): the server
|
|
3669
|
+
# folds read_inbox INTO the poll when there is pending mail —
|
|
3670
|
+
# a message-carrying cycle is 1 RPC instead of 2. mc_wait_poll
|
|
3671
|
+
# v5 (5-arg, default false) serves old SDKs unchanged.
|
|
3672
|
+
"p_drain_inbox": True,
|
|
3668
3673
|
})
|
|
3669
3674
|
if isinstance(resp, dict) and resp.get("ok"):
|
|
3670
3675
|
return {
|
|
@@ -3677,6 +3682,9 @@ def _wait_poll_or_legacy() -> Dict[str, Any]:
|
|
|
3677
3682
|
# Surface it so the caller folds that RPC away. None on
|
|
3678
3683
|
# the legacy path = caller must count_pending itself.
|
|
3679
3684
|
"pending_count": resp.get("pending_count"),
|
|
3685
|
+
# mig 521: pre-drained mc_read_inbox result (same shape the
|
|
3686
|
+
# RPC returns) — present only when pending_count>0.
|
|
3687
|
+
"inbox": resp.get("inbox"),
|
|
3680
3688
|
"_via": "mc_wait_poll",
|
|
3681
3689
|
}
|
|
3682
3690
|
except Exception as e:
|
|
@@ -3687,21 +3695,30 @@ def _wait_poll_or_legacy() -> Dict[str, Any]:
|
|
|
3687
3695
|
"stop": _check_stop_request(),
|
|
3688
3696
|
"tasks": _get_pending_tasks_summary(),
|
|
3689
3697
|
"pending_count": None,
|
|
3698
|
+
"inbox": None,
|
|
3690
3699
|
"_via": "legacy",
|
|
3691
3700
|
}
|
|
3692
3701
|
|
|
3693
3702
|
|
|
3694
|
-
def _drain_unread_response(include_acks: bool
|
|
3703
|
+
def _drain_unread_response(include_acks: bool,
|
|
3704
|
+
rpc_result: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
|
|
3695
3705
|
"""SDK-EFF (task ab1f9f5a): read+mark unread DB messages and shape them as
|
|
3696
3706
|
a wait response. Extracted from the inner-loop's old final-fallback tail so
|
|
3697
3707
|
the outer loop can invoke it only when mc_wait_poll reports pending_count>0
|
|
3698
3708
|
(was: an unconditional count_pending every idle cycle). Returns None when
|
|
3699
|
-
nothing real (acks only / already seen) was found.
|
|
3709
|
+
nothing real (acks only / already seen) was found.
|
|
3710
|
+
|
|
3711
|
+
rpc_result (mig 521, proposal 0bd63160): pre-drained mc_read_inbox payload
|
|
3712
|
+
embedded in the mc_wait_poll v5 response ('inbox' key). When given, NO
|
|
3713
|
+
read_inbox RPC fires — backend.read_inbox still runs its full post-process
|
|
3714
|
+
(decrypt, memory_hints, ack) on the embedded payload, so semantics are
|
|
3715
|
+
byte-identical to the 2-RPC path."""
|
|
3700
3716
|
try:
|
|
3701
3717
|
api_key = _get_api_key()
|
|
3702
3718
|
if not api_key:
|
|
3703
3719
|
return None
|
|
3704
|
-
raw = be.read_inbox(_PROJECT_ID, AGENT_NAME, mark_read=True, api_key=api_key
|
|
3720
|
+
raw = be.read_inbox(_PROJECT_ID, AGENT_NAME, mark_read=True, api_key=api_key,
|
|
3721
|
+
rpc_result=rpc_result)
|
|
3705
3722
|
if not raw:
|
|
3706
3723
|
return None
|
|
3707
3724
|
msgs = [
|
|
@@ -4591,7 +4608,10 @@ async def meshcode_wait(timeout_seconds: int = 20, include_acks: bool = False) -
|
|
|
4591
4608
|
except Exception:
|
|
4592
4609
|
_db_pending = 0
|
|
4593
4610
|
if _db_pending > 0:
|
|
4594
|
-
|
|
4611
|
+
# mig 521: the poll already drained the inbox server-side
|
|
4612
|
+
# (1 RPC); pass it through. None => classic read_inbox RPC.
|
|
4613
|
+
_drained = _drain_unread_response(include_acks,
|
|
4614
|
+
rpc_result=_wp.get("inbox"))
|
|
4595
4615
|
if _drained:
|
|
4596
4616
|
result = _drained
|
|
4597
4617
|
_set_state("online", "")
|
|
@@ -5348,6 +5368,64 @@ def meshcode_tasks(status_filter: Optional[str] = None, verbose: bool = False) -
|
|
|
5348
5368
|
return {"ok": True, "tasks": compact}
|
|
5349
5369
|
|
|
5350
5370
|
|
|
5371
|
+
def _auto_task_progress(task_id: str, pct: int, note: str) -> None:
|
|
5372
|
+
"""Best-effort lifecycle progress floor (task 6b2afa5d: pbar accuracy).
|
|
5373
|
+
|
|
5374
|
+
Writes pct via mc_task_progress (mig 516) ONLY if the task's current
|
|
5375
|
+
reported pct is unknown or lower — a re-claimed task that was already at
|
|
5376
|
+
60% must never get reset to the claim/start milestone. Swallows every
|
|
5377
|
+
failure (RPC missing pre-mig-516, auth, state): the lifecycle action this
|
|
5378
|
+
rides on must never be blocked by progress telemetry.
|
|
5379
|
+
"""
|
|
5380
|
+
try:
|
|
5381
|
+
api_key = _get_api_key()
|
|
5382
|
+
if pct < 100: # 100 is monotonic by definition — skip the read
|
|
5383
|
+
listing = be.task_list(api_key, _PROJECT_ID, AGENT_NAME,
|
|
5384
|
+
status_filter=None, include_done=False)
|
|
5385
|
+
if isinstance(listing, dict) and listing.get("ok"):
|
|
5386
|
+
for t in listing.get("tasks", []):
|
|
5387
|
+
if t.get("id") == task_id:
|
|
5388
|
+
current = t.get("progress_pct")
|
|
5389
|
+
if isinstance(current, int) and current >= pct:
|
|
5390
|
+
return # real progress already reported — keep it
|
|
5391
|
+
break
|
|
5392
|
+
be.task_progress(api_key, _PROJECT_ID, task_id, pct, note)
|
|
5393
|
+
except Exception:
|
|
5394
|
+
pass # auto-progress is a fallback, never a dependency
|
|
5395
|
+
|
|
5396
|
+
|
|
5397
|
+
@mcp.tool()
|
|
5398
|
+
@with_working_status
|
|
5399
|
+
def meshcode_task_progress(task_id: str, pct: int, note: str = "") -> Dict[str, Any]:
|
|
5400
|
+
"""Report REAL progress (0-100) on your claimed/in_progress task — feeds the dashboard pbar.
|
|
5401
|
+
|
|
5402
|
+
Samuel directive (250e131e / msg d88bb9c7): every in_progress task must
|
|
5403
|
+
report advance at real milestones, not vibes. Call at meaningful points
|
|
5404
|
+
(e.g. 10 on claim, 25-40 design done, 70 code+tests, 90 smoke green).
|
|
5405
|
+
Server-side mc_task_progress (mig 516) also extends your claim lease 4h
|
|
5406
|
+
and appends {ts, by, pct, note} to deliverables.progress_log.
|
|
5407
|
+
|
|
5408
|
+
Args:
|
|
5409
|
+
task_id: task you hold the claim on (UUID or 8-char prefix NOT
|
|
5410
|
+
accepted — pass the full UUID).
|
|
5411
|
+
pct: integer 0-100. Monotonicity is NOT enforced — you can correct
|
|
5412
|
+
an overestimate downward.
|
|
5413
|
+
note: short human-readable milestone (shows in the panel tooltip).
|
|
5414
|
+
"""
|
|
5415
|
+
if not isinstance(pct, int) or pct < 0 or pct > 100:
|
|
5416
|
+
return {"ok": False, "error": "pct must be an integer 0-100",
|
|
5417
|
+
"error_code": "invalid_arg"}
|
|
5418
|
+
api_key = _get_api_key()
|
|
5419
|
+
result = be.task_progress(api_key, _PROJECT_ID, task_id, pct, note or None)
|
|
5420
|
+
if isinstance(result, dict) and result.get("error"):
|
|
5421
|
+
err = result.get("error")
|
|
5422
|
+
msg = err.get("message", "") if isinstance(err, dict) else str(err)
|
|
5423
|
+
if "Could not find the function" in msg or "PGRST202" in msg:
|
|
5424
|
+
return {"ok": False, "error_code": "rpc_missing",
|
|
5425
|
+
"error": "mc_task_progress RPC not deployed (mig 516 not applied) — progress not recorded, work unaffected"}
|
|
5426
|
+
return result
|
|
5427
|
+
|
|
5428
|
+
|
|
5351
5429
|
@mcp.tool()
|
|
5352
5430
|
@with_working_status
|
|
5353
5431
|
def meshcode_task_claim(task_id: str) -> Dict[str, Any]:
|
|
@@ -5368,6 +5446,8 @@ def meshcode_task_claim(task_id: str) -> Dict[str, Any]:
|
|
|
5368
5446
|
api_key=api_key)
|
|
5369
5447
|
except Exception:
|
|
5370
5448
|
pass # status surfacing is best-effort
|
|
5449
|
+
# pbar accuracy fallback (task 6b2afa5d): claim = at least 10%
|
|
5450
|
+
_auto_task_progress(resp.get("task_id") or task_id, 10, "auto: claimed")
|
|
5371
5451
|
return resp
|
|
5372
5452
|
|
|
5373
5453
|
|
|
@@ -5399,6 +5479,10 @@ def meshcode_task_complete(task_id: str, summary: str = "", force: bool = False)
|
|
|
5399
5479
|
}
|
|
5400
5480
|
except Exception:
|
|
5401
5481
|
pass # Best-effort check; don't block on listing failure.
|
|
5482
|
+
# pbar accuracy (task 6b2afa5d): stamp 100 BEFORE complete — mc_task_progress
|
|
5483
|
+
# only accepts claimed/in_progress/in_review, so post-complete is too late,
|
|
5484
|
+
# and a done task otherwise freezes at its last reported pct on the panel.
|
|
5485
|
+
_auto_task_progress(task_id, 100, "auto: completed")
|
|
5402
5486
|
result = be.task_complete(api_key, _PROJECT_ID, task_id, AGENT_NAME, summary=summary)
|
|
5403
5487
|
# Task data persists in the task system — do NOT duplicate to memory.
|
|
5404
5488
|
# Samuel: "los tasks no deben guardarse en memoria, para eso salen en tasks"
|
|
@@ -5428,12 +5512,16 @@ def meshcode_task_complete(task_id: str, summary: str = "", force: bool = False)
|
|
|
5428
5512
|
def meshcode_task_start(task_id: str) -> Dict[str, Any]:
|
|
5429
5513
|
"""Flip a claimed task to in_progress (mig 349 invariant: one in_progress per agent — any sibling in_progress for this agent is demoted to 'claimed' atomically)."""
|
|
5430
5514
|
api_key = _get_api_key()
|
|
5431
|
-
|
|
5515
|
+
result = be.sb_rpc("mc_task_start", {
|
|
5432
5516
|
"p_api_key": api_key,
|
|
5433
5517
|
"p_project_id": _PROJECT_ID,
|
|
5434
5518
|
"p_task_id": task_id,
|
|
5435
5519
|
"p_starting_agent": AGENT_NAME,
|
|
5436
5520
|
})
|
|
5521
|
+
if isinstance(result, dict) and result.get("ok"):
|
|
5522
|
+
# pbar accuracy fallback (task 6b2afa5d): started = at least 25%
|
|
5523
|
+
_auto_task_progress(task_id, 25, "auto: started")
|
|
5524
|
+
return result
|
|
5437
5525
|
|
|
5438
5526
|
|
|
5439
5527
|
@mcp.tool()
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Tests for the pbar-accuracy wheel half (task 6b2afa5d).
|
|
2
|
+
|
|
3
|
+
Samuel (msg d88bb9c7): "la barra de progreso no es accurate". Root cause:
|
|
4
|
+
mig 516 shipped the server-side RPC public.mc_task_progress, but the wheel
|
|
5
|
+
MCP never exposed a wrapper (QA msg ba932abc) — agents physically could not
|
|
6
|
+
report real pct, so the panel pbar showed elapsed-time guesses or froze.
|
|
7
|
+
|
|
8
|
+
This file asserts the contract of the fix:
|
|
9
|
+
1. backend.task_progress maps args to the mig-516 RPC signature.
|
|
10
|
+
2. server.py exposes the meshcode_task_progress MCP tool.
|
|
11
|
+
3. Lifecycle auto-progress fallback exists: claim->10, start->25,
|
|
12
|
+
complete->100 — all routed through _auto_task_progress, which is
|
|
13
|
+
monotonic-guarded (never resets a re-claimed 60% task to 10%) and
|
|
14
|
+
best-effort (never blocks the lifecycle action).
|
|
15
|
+
4. The complete->100 stamp happens BEFORE be.task_complete in source
|
|
16
|
+
order (mc_task_progress refuses status='done', so after is too late).
|
|
17
|
+
5. mc_task_progress is on the agent-callable RPC allowlist.
|
|
18
|
+
|
|
19
|
+
server.py has module-level side effects requiring live Supabase, so the
|
|
20
|
+
server-side assertions are static (source/AST) — same pattern as
|
|
21
|
+
test_wait_open_tasks_contradiction.py. backend.py imports clean, so the
|
|
22
|
+
param-mapping test runs the real function with sb_rpc monkeypatched.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import unittest
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
REPO = Path(__file__).resolve().parent.parent
|
|
31
|
+
SERVER_PY = REPO / "meshcode" / "meshcode_mcp" / "server.py"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestBackendTaskProgress(unittest.TestCase):
|
|
35
|
+
"""backend.task_progress → mc_task_progress param mapping (mocked RPC)."""
|
|
36
|
+
|
|
37
|
+
def test_param_mapping_matches_mig_516_signature(self):
|
|
38
|
+
from meshcode.meshcode_mcp import backend as be
|
|
39
|
+
captured = {}
|
|
40
|
+
|
|
41
|
+
def fake_rpc(fn_name, params, **kw):
|
|
42
|
+
captured["fn"] = fn_name
|
|
43
|
+
captured["params"] = params
|
|
44
|
+
return {"ok": True, "task_id": params["p_task_id"], "progress_pct": params["p_pct"]}
|
|
45
|
+
|
|
46
|
+
orig = be.sb_rpc
|
|
47
|
+
be.sb_rpc = fake_rpc
|
|
48
|
+
try:
|
|
49
|
+
out = be.task_progress("key-x", "proj-1", "task-1", 40, "mitad")
|
|
50
|
+
finally:
|
|
51
|
+
be.sb_rpc = orig
|
|
52
|
+
|
|
53
|
+
self.assertEqual(captured["fn"], "mc_task_progress")
|
|
54
|
+
self.assertEqual(captured["params"], {
|
|
55
|
+
"p_api_key": "key-x",
|
|
56
|
+
"p_project_id": "proj-1",
|
|
57
|
+
"p_task_id": "task-1",
|
|
58
|
+
"p_pct": 40,
|
|
59
|
+
"p_note": "mitad",
|
|
60
|
+
})
|
|
61
|
+
self.assertTrue(out["ok"])
|
|
62
|
+
|
|
63
|
+
def test_note_defaults_to_none(self):
|
|
64
|
+
from meshcode.meshcode_mcp import backend as be
|
|
65
|
+
captured = {}
|
|
66
|
+
|
|
67
|
+
def fake_rpc(fn_name, params, **kw):
|
|
68
|
+
captured["params"] = params
|
|
69
|
+
return {"ok": True}
|
|
70
|
+
|
|
71
|
+
orig = be.sb_rpc
|
|
72
|
+
be.sb_rpc = fake_rpc
|
|
73
|
+
try:
|
|
74
|
+
be.task_progress("k", "p", "t", 70)
|
|
75
|
+
finally:
|
|
76
|
+
be.sb_rpc = orig
|
|
77
|
+
self.assertIsNone(captured["params"]["p_note"])
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TestServerWiring(unittest.TestCase):
|
|
81
|
+
"""Static assertions on server.py — tool + lifecycle hooks."""
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def setUpClass(cls):
|
|
85
|
+
cls.src = SERVER_PY.read_text(encoding="utf-8")
|
|
86
|
+
|
|
87
|
+
def test_tool_exposed(self):
|
|
88
|
+
self.assertIn("def meshcode_task_progress(", self.src,
|
|
89
|
+
"wheel must expose the meshcode_task_progress MCP tool "
|
|
90
|
+
"(the missing wrapper WAS the bug — QA msg ba932abc)")
|
|
91
|
+
|
|
92
|
+
def test_tool_validates_pct_client_side(self):
|
|
93
|
+
body = self._fn_body("def meshcode_task_progress(")
|
|
94
|
+
self.assertIn("pct must be an integer 0-100", body,
|
|
95
|
+
"tool must reject bad pct before burning an RPC")
|
|
96
|
+
|
|
97
|
+
def test_auto_progress_helper_exists_and_is_guarded(self):
|
|
98
|
+
self.assertIn("def _auto_task_progress(", self.src)
|
|
99
|
+
body = self._fn_body("def _auto_task_progress(")
|
|
100
|
+
self.assertIn("current >= pct", body,
|
|
101
|
+
"monotonic guard: a re-claimed task at 60% must not be "
|
|
102
|
+
"reset to the 10/25 lifecycle floor")
|
|
103
|
+
self.assertIn("except Exception", body,
|
|
104
|
+
"auto-progress must swallow all failures — it is a "
|
|
105
|
+
"fallback, never a dependency")
|
|
106
|
+
|
|
107
|
+
def test_lifecycle_hooks_present(self):
|
|
108
|
+
self.assertIn('"auto: claimed"', self.src, "claim → 10 hook missing")
|
|
109
|
+
self.assertIn('"auto: started"', self.src, "start → 25 hook missing")
|
|
110
|
+
self.assertIn('"auto: completed"', self.src, "complete → 100 hook missing")
|
|
111
|
+
|
|
112
|
+
def test_complete_stamps_100_before_rpc(self):
|
|
113
|
+
# mc_task_progress refuses status='done' — the 100 stamp must come
|
|
114
|
+
# BEFORE be.task_complete in meshcode_task_complete's body.
|
|
115
|
+
body = self._fn_body("def meshcode_task_complete(")
|
|
116
|
+
idx_stamp = body.find('_auto_task_progress(task_id, 100')
|
|
117
|
+
idx_complete = body.find("be.task_complete(")
|
|
118
|
+
self.assertGreater(idx_stamp, 0, "complete must stamp 100")
|
|
119
|
+
self.assertGreater(idx_complete, idx_stamp,
|
|
120
|
+
"100 stamp must precede be.task_complete — after "
|
|
121
|
+
"complete the task is 'done' and the RPC refuses")
|
|
122
|
+
|
|
123
|
+
def test_task_marker_present(self):
|
|
124
|
+
self.assertIn("6b2afa5d", self.src,
|
|
125
|
+
"code must reference the task id for future git-blame")
|
|
126
|
+
|
|
127
|
+
def _fn_body(self, needle: str) -> str:
|
|
128
|
+
start = self.src.find(needle)
|
|
129
|
+
assert start > 0, f"{needle!r} not found"
|
|
130
|
+
# body ends at the next top-level decorator/def
|
|
131
|
+
end = self.src.find("\n@mcp.tool()", start)
|
|
132
|
+
if end < 0:
|
|
133
|
+
end = self.src.find("\ndef ", start + 1)
|
|
134
|
+
return self.src[start:end if end > 0 else len(self.src)]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class TestAllowlist(unittest.TestCase):
|
|
138
|
+
|
|
139
|
+
def test_mc_task_progress_on_allowlist(self):
|
|
140
|
+
from meshcode.rpc_allowlist import AGENT_CALLABLE_RPCS
|
|
141
|
+
self.assertIn("mc_task_progress", AGENT_CALLABLE_RPCS,
|
|
142
|
+
"mig 516 grants it to anon — the allowlist is the "
|
|
143
|
+
"single source of truth and must agree")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
unittest.main()
|
|
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
|
|
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
|