meshcode 2.11.133__tar.gz → 2.11.134__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.134}/PKG-INFO +1 -1
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/__init__.py +1 -1
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/meshcode_mcp/backend.py +16 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/meshcode_mcp/server.py +69 -1
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/rpc_allowlist.py +1 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode.egg-info/SOURCES.txt +1 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/pyproject.toml +1 -1
- meshcode-2.11.134/tests/test_task_progress.py +147 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/README.md +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/__main__.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/_session_handoff_template.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/cli.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/compat.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/daemon.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/doctor.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/hooks/__init__.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/hooks/repo_path_lock.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/hostd.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/invites.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/launcher.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/meshcode_mcp/swarm.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/meshcode_mcp/test_swarm.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/preferences.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/protocol_handler.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/run_agent.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/secrets.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/self_update.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/up.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode/upload.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/setup.cfg +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_autonomous_prompt_inject.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_core.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_doctor.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_hostd_zombie_sessions.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_live_mesh_guard.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_session_replay_gate.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_stop_ghost_terminal.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_swarm_events.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_terminal_lifecycle.py +0 -0
- {meshcode-2.11.133 → meshcode-2.11.134}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -1389,6 +1389,22 @@ def task_complete(api_key, project_id, task_id, completing_agent, summary=""):
|
|
|
1389
1389
|
})
|
|
1390
1390
|
|
|
1391
1391
|
|
|
1392
|
+
def task_progress(api_key, project_id, task_id, pct, note=None):
|
|
1393
|
+
"""Report real progress pct (0-100) on a claimed/in_progress/in_review task.
|
|
1394
|
+
|
|
1395
|
+
Server-side: public.mc_task_progress (mig 516) — claim-holder or owner;
|
|
1396
|
+
updates progress_pct/note/last_progress_at, extends the claim lease 4h,
|
|
1397
|
+
appends to deliverables.progress_log. The dashboard pbar reads this as
|
|
1398
|
+
progress_source='reported' (task 6b2afa5d: pbar accuracy)."""
|
|
1399
|
+
return sb_rpc("mc_task_progress", {
|
|
1400
|
+
"p_api_key": api_key,
|
|
1401
|
+
"p_project_id": project_id,
|
|
1402
|
+
"p_task_id": task_id,
|
|
1403
|
+
"p_pct": pct,
|
|
1404
|
+
"p_note": note,
|
|
1405
|
+
})
|
|
1406
|
+
|
|
1407
|
+
|
|
1392
1408
|
def feature_flag_enabled(api_key, flag_name):
|
|
1393
1409
|
"""Agent-facing feature-flag check (mig478). The FE's mc_check_feature_flag
|
|
1394
1410
|
is auth.uid()-only, so agents use this api-key variant. Returns the raw
|
|
@@ -5348,6 +5348,64 @@ def meshcode_tasks(status_filter: Optional[str] = None, verbose: bool = False) -
|
|
|
5348
5348
|
return {"ok": True, "tasks": compact}
|
|
5349
5349
|
|
|
5350
5350
|
|
|
5351
|
+
def _auto_task_progress(task_id: str, pct: int, note: str) -> None:
|
|
5352
|
+
"""Best-effort lifecycle progress floor (task 6b2afa5d: pbar accuracy).
|
|
5353
|
+
|
|
5354
|
+
Writes pct via mc_task_progress (mig 516) ONLY if the task's current
|
|
5355
|
+
reported pct is unknown or lower — a re-claimed task that was already at
|
|
5356
|
+
60% must never get reset to the claim/start milestone. Swallows every
|
|
5357
|
+
failure (RPC missing pre-mig-516, auth, state): the lifecycle action this
|
|
5358
|
+
rides on must never be blocked by progress telemetry.
|
|
5359
|
+
"""
|
|
5360
|
+
try:
|
|
5361
|
+
api_key = _get_api_key()
|
|
5362
|
+
if pct < 100: # 100 is monotonic by definition — skip the read
|
|
5363
|
+
listing = be.task_list(api_key, _PROJECT_ID, AGENT_NAME,
|
|
5364
|
+
status_filter=None, include_done=False)
|
|
5365
|
+
if isinstance(listing, dict) and listing.get("ok"):
|
|
5366
|
+
for t in listing.get("tasks", []):
|
|
5367
|
+
if t.get("id") == task_id:
|
|
5368
|
+
current = t.get("progress_pct")
|
|
5369
|
+
if isinstance(current, int) and current >= pct:
|
|
5370
|
+
return # real progress already reported — keep it
|
|
5371
|
+
break
|
|
5372
|
+
be.task_progress(api_key, _PROJECT_ID, task_id, pct, note)
|
|
5373
|
+
except Exception:
|
|
5374
|
+
pass # auto-progress is a fallback, never a dependency
|
|
5375
|
+
|
|
5376
|
+
|
|
5377
|
+
@mcp.tool()
|
|
5378
|
+
@with_working_status
|
|
5379
|
+
def meshcode_task_progress(task_id: str, pct: int, note: str = "") -> Dict[str, Any]:
|
|
5380
|
+
"""Report REAL progress (0-100) on your claimed/in_progress task — feeds the dashboard pbar.
|
|
5381
|
+
|
|
5382
|
+
Samuel directive (250e131e / msg d88bb9c7): every in_progress task must
|
|
5383
|
+
report advance at real milestones, not vibes. Call at meaningful points
|
|
5384
|
+
(e.g. 10 on claim, 25-40 design done, 70 code+tests, 90 smoke green).
|
|
5385
|
+
Server-side mc_task_progress (mig 516) also extends your claim lease 4h
|
|
5386
|
+
and appends {ts, by, pct, note} to deliverables.progress_log.
|
|
5387
|
+
|
|
5388
|
+
Args:
|
|
5389
|
+
task_id: task you hold the claim on (UUID or 8-char prefix NOT
|
|
5390
|
+
accepted — pass the full UUID).
|
|
5391
|
+
pct: integer 0-100. Monotonicity is NOT enforced — you can correct
|
|
5392
|
+
an overestimate downward.
|
|
5393
|
+
note: short human-readable milestone (shows in the panel tooltip).
|
|
5394
|
+
"""
|
|
5395
|
+
if not isinstance(pct, int) or pct < 0 or pct > 100:
|
|
5396
|
+
return {"ok": False, "error": "pct must be an integer 0-100",
|
|
5397
|
+
"error_code": "invalid_arg"}
|
|
5398
|
+
api_key = _get_api_key()
|
|
5399
|
+
result = be.task_progress(api_key, _PROJECT_ID, task_id, pct, note or None)
|
|
5400
|
+
if isinstance(result, dict) and result.get("error"):
|
|
5401
|
+
err = result.get("error")
|
|
5402
|
+
msg = err.get("message", "") if isinstance(err, dict) else str(err)
|
|
5403
|
+
if "Could not find the function" in msg or "PGRST202" in msg:
|
|
5404
|
+
return {"ok": False, "error_code": "rpc_missing",
|
|
5405
|
+
"error": "mc_task_progress RPC not deployed (mig 516 not applied) — progress not recorded, work unaffected"}
|
|
5406
|
+
return result
|
|
5407
|
+
|
|
5408
|
+
|
|
5351
5409
|
@mcp.tool()
|
|
5352
5410
|
@with_working_status
|
|
5353
5411
|
def meshcode_task_claim(task_id: str) -> Dict[str, Any]:
|
|
@@ -5368,6 +5426,8 @@ def meshcode_task_claim(task_id: str) -> Dict[str, Any]:
|
|
|
5368
5426
|
api_key=api_key)
|
|
5369
5427
|
except Exception:
|
|
5370
5428
|
pass # status surfacing is best-effort
|
|
5429
|
+
# pbar accuracy fallback (task 6b2afa5d): claim = at least 10%
|
|
5430
|
+
_auto_task_progress(resp.get("task_id") or task_id, 10, "auto: claimed")
|
|
5371
5431
|
return resp
|
|
5372
5432
|
|
|
5373
5433
|
|
|
@@ -5399,6 +5459,10 @@ def meshcode_task_complete(task_id: str, summary: str = "", force: bool = False)
|
|
|
5399
5459
|
}
|
|
5400
5460
|
except Exception:
|
|
5401
5461
|
pass # Best-effort check; don't block on listing failure.
|
|
5462
|
+
# pbar accuracy (task 6b2afa5d): stamp 100 BEFORE complete — mc_task_progress
|
|
5463
|
+
# only accepts claimed/in_progress/in_review, so post-complete is too late,
|
|
5464
|
+
# and a done task otherwise freezes at its last reported pct on the panel.
|
|
5465
|
+
_auto_task_progress(task_id, 100, "auto: completed")
|
|
5402
5466
|
result = be.task_complete(api_key, _PROJECT_ID, task_id, AGENT_NAME, summary=summary)
|
|
5403
5467
|
# Task data persists in the task system — do NOT duplicate to memory.
|
|
5404
5468
|
# Samuel: "los tasks no deben guardarse en memoria, para eso salen en tasks"
|
|
@@ -5428,12 +5492,16 @@ def meshcode_task_complete(task_id: str, summary: str = "", force: bool = False)
|
|
|
5428
5492
|
def meshcode_task_start(task_id: str) -> Dict[str, Any]:
|
|
5429
5493
|
"""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
5494
|
api_key = _get_api_key()
|
|
5431
|
-
|
|
5495
|
+
result = be.sb_rpc("mc_task_start", {
|
|
5432
5496
|
"p_api_key": api_key,
|
|
5433
5497
|
"p_project_id": _PROJECT_ID,
|
|
5434
5498
|
"p_task_id": task_id,
|
|
5435
5499
|
"p_starting_agent": AGENT_NAME,
|
|
5436
5500
|
})
|
|
5501
|
+
if isinstance(result, dict) and result.get("ok"):
|
|
5502
|
+
# pbar accuracy fallback (task 6b2afa5d): started = at least 25%
|
|
5503
|
+
_auto_task_progress(task_id, 25, "auto: started")
|
|
5504
|
+
return result
|
|
5437
5505
|
|
|
5438
5506
|
|
|
5439
5507
|
@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
|
|
File without changes
|