meshcode 2.11.121__tar.gz → 2.11.123__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.121 → meshcode-2.11.123}/PKG-INFO +1 -1
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/__init__.py +1 -1
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/hostd.py +64 -1
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/meshcode_mcp/server.py +49 -19
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/meshcode_mcp/sleep_signals.py +30 -1
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.121 → meshcode-2.11.123}/pyproject.toml +1 -1
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_sleep_signals.py +48 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/README.md +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/__main__.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/_session_handoff_template.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/cli.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/compat.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/daemon.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/doctor.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/hooks/__init__.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/hooks/repo_path_lock.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/invites.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/launcher.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/preferences.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/protocol_handler.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/rpc_allowlist.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/run_agent.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/secrets.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/self_update.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/up.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode/upload.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/setup.cfg +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_autonomous_prompt_inject.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_core.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_doctor.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_swarm_events.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.123}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -467,7 +467,15 @@ def _do_respawns(api_key: str, host_id: str) -> int:
|
|
|
467
467
|
if not _BOOT_AUTOSTART:
|
|
468
468
|
_spawn_age = c.get("spawned_age_s")
|
|
469
469
|
_hostd_uptime = time.time() - _HOSTD_STARTED_AT
|
|
470
|
-
|
|
470
|
+
# ALIVE-ON-OUR-WATCH bypass (task baefc8ab part C — live miss 2026-06-10T00:31Z:
|
|
471
|
+
# front-end exited for respawn minutes after a hostd restart; its spawned_at predated
|
|
472
|
+
# the restart, the gate read it as a boot-stale leftover and ATE the respawn until a
|
|
473
|
+
# manual Start). An agent that HEARTBEATED during this daemon's lifetime is not a
|
|
474
|
+
# reboot leftover — it lived and died on our watch, so its respawn is legitimate.
|
|
475
|
+
# A true boot-stale leftover has heartbeat_age_s >= uptime (or None).
|
|
476
|
+
_hb_age = c.get("heartbeat_age_s")
|
|
477
|
+
_alive_on_our_watch = _hb_age is not None and _hb_age < _hostd_uptime
|
|
478
|
+
if (_spawn_age is None or _spawn_age >= _hostd_uptime) and not _alive_on_our_watch:
|
|
471
479
|
_log(f"BOOT-AUTOSTART OFF: skip auto-respawn {proj}/{agent} (desired_state=running set "
|
|
472
480
|
f"before this hostd start — boot-stale leftover; explicit launch required). "
|
|
473
481
|
f"Set MESHCODE_BOOT_AUTOSTART=1 to auto-launch at boot.")
|
|
@@ -1762,6 +1770,52 @@ def _do_version_recycles(api_key: str, host_id: str) -> int:
|
|
|
1762
1770
|
return n
|
|
1763
1771
|
|
|
1764
1772
|
|
|
1773
|
+
# c301be69: hold the singleton lock handle for the daemon's lifetime — module
|
|
1774
|
+
# global so it's never GC'd (GC would close the fd and release the lock).
|
|
1775
|
+
_HOSTD_LOCK_FH = None
|
|
1776
|
+
|
|
1777
|
+
|
|
1778
|
+
def _acquire_hostd_singleton():
|
|
1779
|
+
"""One hostd per machine (task c301be69 — duplicate-daemon race observed
|
|
1780
|
+
live 2026-06-09: a manual `nohup meshcode hostd run` raced auto-bootstrap,
|
|
1781
|
+
TWO daemons ran ~30s = double respawn/recycle sweeps = storm class).
|
|
1782
|
+
|
|
1783
|
+
Exclusive flock on ~/.meshcode/hostd.lock, held for the process lifetime.
|
|
1784
|
+
The OS releases it on ANY death (crash, kill -9), so there is no
|
|
1785
|
+
stale-pidfile problem; the pid written inside is informational only.
|
|
1786
|
+
Python opens fds CLOEXEC (PEP 446), so the self-reexec-on-version-drift
|
|
1787
|
+
path releases the lock at exec time and the fresh image re-acquires it.
|
|
1788
|
+
|
|
1789
|
+
Returns (fh, status): status in 'acquired' | 'held' | 'error'.
|
|
1790
|
+
'error' means the lock could not be evaluated — callers FAIL OPEN
|
|
1791
|
+
(one possibly-duplicate daemon beats no daemon at all)."""
|
|
1792
|
+
try:
|
|
1793
|
+
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
1794
|
+
fh = open(STATE_DIR / "hostd.lock", "a+")
|
|
1795
|
+
try:
|
|
1796
|
+
if sys.platform == "win32":
|
|
1797
|
+
import msvcrt
|
|
1798
|
+
fh.seek(0)
|
|
1799
|
+
msvcrt.locking(fh.fileno(), msvcrt.LK_NBLCK, 1)
|
|
1800
|
+
else:
|
|
1801
|
+
import fcntl
|
|
1802
|
+
fcntl.flock(fh.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
1803
|
+
except OSError:
|
|
1804
|
+
fh.close()
|
|
1805
|
+
return (None, "held")
|
|
1806
|
+
try:
|
|
1807
|
+
fh.seek(0)
|
|
1808
|
+
fh.truncate()
|
|
1809
|
+
fh.write(str(os.getpid()))
|
|
1810
|
+
fh.flush()
|
|
1811
|
+
except Exception:
|
|
1812
|
+
pass
|
|
1813
|
+
return (fh, "acquired")
|
|
1814
|
+
except Exception as e:
|
|
1815
|
+
_log(f"WARN singleton lock unavailable ({e}) — proceeding unlocked (fail-open)")
|
|
1816
|
+
return (None, "error")
|
|
1817
|
+
|
|
1818
|
+
|
|
1765
1819
|
def cmd_hostd(args: list) -> int:
|
|
1766
1820
|
"""Entry point for `meshcode hostd ...`."""
|
|
1767
1821
|
if not args or args[0] in ("-h", "--help"):
|
|
@@ -1790,6 +1844,15 @@ def cmd_hostd(args: list) -> int:
|
|
|
1790
1844
|
if not api_key:
|
|
1791
1845
|
_log("FATAL: no api key — run `meshcode login` (key is read from the keychain)")
|
|
1792
1846
|
return 1
|
|
1847
|
+
# SINGLETON (task c301be69): second instance exits 0 with one log line.
|
|
1848
|
+
# Covers the manual-run vs auto-bootstrap race; _hostd_bootstrap_if_needed
|
|
1849
|
+
# honors it implicitly — its spawned `hostd run` hits this gate and exits.
|
|
1850
|
+
global _HOSTD_LOCK_FH
|
|
1851
|
+
_lk, _lk_status = _acquire_hostd_singleton()
|
|
1852
|
+
if _lk_status == "held":
|
|
1853
|
+
_log("hostd already running on this machine (hostd.lock held) — exiting (singleton c301be69)")
|
|
1854
|
+
return 0
|
|
1855
|
+
_HOSTD_LOCK_FH = _lk # may be None on 'error' (fail-open)
|
|
1793
1856
|
# P1 hostd_CRASH_LOOP: the detached Windows daemon has NO console, so crashes (incl hard
|
|
1794
1857
|
# faults / uncaught exceptions) went to INVISIBLE stderr — hostd.log showed nothing. Capture
|
|
1795
1858
|
# stderr + faulthandler tracebacks to a file + install an excepthook so the NEXT crash is
|
|
@@ -4109,11 +4109,14 @@ def _try_auto_complete_on_ship(payload: Dict[str, Any]) -> Optional[Dict[str, An
|
|
|
4109
4109
|
return None
|
|
4110
4110
|
|
|
4111
4111
|
|
|
4112
|
-
def
|
|
4113
|
-
"""CLOSEGAP-3
|
|
4114
|
-
in_progress
|
|
4115
|
-
|
|
4116
|
-
|
|
4112
|
+
def _try_auto_park_on_done_status(task_hint: str, status: str = "done") -> Optional[Dict[str, Any]]:
|
|
4113
|
+
"""CLOSEGAP-3 v2 (task 53767993): when set_status='done'/'sleeping' with
|
|
4114
|
+
exactly ONE in_progress claim, PARK it — roll back to 'claimed' with a
|
|
4115
|
+
checkpoint note (mc_task_checkpoint_park, mig 476) — NEVER complete it.
|
|
4116
|
+
Live incident 2026-06-09T23:30Z: the old auto-COMPLETE closed 250b7c1e as
|
|
4117
|
+
done with zero work at an ordered exit-for-respawn (board lied to Samuel;
|
|
4118
|
+
re-opened as 7a467894). status='done' may now ONLY come from an explicit
|
|
4119
|
+
meshcode_task_complete. No-op for 0 or >1 active claims."""
|
|
4117
4120
|
try:
|
|
4118
4121
|
api_key = _get_api_key()
|
|
4119
4122
|
if not api_key:
|
|
@@ -4128,24 +4131,30 @@ def _try_auto_complete_on_done_status(task_hint: str) -> Optional[Dict[str, Any]
|
|
|
4128
4131
|
if len(active) != 1:
|
|
4129
4132
|
return None
|
|
4130
4133
|
target = active[0]
|
|
4131
|
-
|
|
4132
|
-
f"
|
|
4133
|
-
f"Task hint: {(task_hint or '(none)')[:200]}. "
|
|
4134
|
-
f"
|
|
4134
|
+
note = (
|
|
4135
|
+
f"CHECKPOINT (CLOSEGAP-3 park): agent flipped set_status='{status}' "
|
|
4136
|
+
f"mid-claim (rotation/sleep). Task hint: {(task_hint or '(none)')[:200]}. "
|
|
4137
|
+
f"Work NOT verified complete — resuming agent must task_complete explicitly."
|
|
4135
4138
|
)
|
|
4136
|
-
result = be.
|
|
4139
|
+
result = be.sb_rpc("mc_task_checkpoint_park", {
|
|
4140
|
+
"p_api_key": api_key,
|
|
4141
|
+
"p_project_id": _PROJECT_ID,
|
|
4142
|
+
"p_task_id": target["id"],
|
|
4143
|
+
"p_agent": AGENT_NAME,
|
|
4144
|
+
"p_note": note,
|
|
4145
|
+
})
|
|
4137
4146
|
if not (isinstance(result, dict) and result.get("ok")):
|
|
4138
4147
|
return None
|
|
4139
4148
|
try:
|
|
4140
4149
|
_log_activity_bg(
|
|
4141
4150
|
"auto_checkpoint",
|
|
4142
|
-
f"{AGENT_NAME}
|
|
4151
|
+
f"{AGENT_NAME} PARKED task {target['id'][:8]} on set_status={status} (claimed again, NOT done)",
|
|
4143
4152
|
)
|
|
4144
4153
|
except Exception:
|
|
4145
4154
|
pass
|
|
4146
|
-
return {"task_id": target["id"][:8], "title": str(target.get("title") or "")[:80]}
|
|
4155
|
+
return {"task_id": target["id"][:8], "title": str(target.get("title") or "")[:80], "parked": True}
|
|
4147
4156
|
except Exception as e:
|
|
4148
|
-
log.debug(f"[meshcode] auto-
|
|
4157
|
+
log.debug(f"[meshcode] auto-park on done-status failed: {e}")
|
|
4149
4158
|
return None
|
|
4150
4159
|
|
|
4151
4160
|
|
|
@@ -5079,15 +5088,15 @@ def meshcode_set_status(status: str, task: str = "") -> Dict[str, Any]:
|
|
|
5079
5088
|
}
|
|
5080
5089
|
except Exception:
|
|
5081
5090
|
pass # best-effort; never block set_status
|
|
5082
|
-
# CLOSEGAP-3 (task
|
|
5083
|
-
#
|
|
5084
|
-
#
|
|
5091
|
+
# CLOSEGAP-3 v2 (task 53767993, was 69dc5a62): when the agent flips
|
|
5092
|
+
# set_status='done'/'sleeping' with exactly one active in_progress claim,
|
|
5093
|
+
# PARK that claim (back to 'claimed' + checkpoint note) — never complete.
|
|
5085
5094
|
# Baseline (NOT autonomous-gated). Best-effort.
|
|
5086
|
-
if status
|
|
5095
|
+
if status in ("done", "sleeping") and isinstance(resp, dict) and resp.get("ok"):
|
|
5087
5096
|
try:
|
|
5088
|
-
_cp =
|
|
5097
|
+
_cp = _try_auto_park_on_done_status(task, status)
|
|
5089
5098
|
if _cp:
|
|
5090
|
-
resp["
|
|
5099
|
+
resp["auto_parked_task"] = _cp
|
|
5091
5100
|
except Exception:
|
|
5092
5101
|
pass
|
|
5093
5102
|
return resp
|
|
@@ -7436,6 +7445,27 @@ def meshcode_auto_wake_toggle(enabled: bool) -> Dict[str, Any]:
|
|
|
7436
7445
|
}
|
|
7437
7446
|
|
|
7438
7447
|
|
|
7448
|
+
@mcp.tool()
|
|
7449
|
+
def meshcode_mesh_keep_alive(enabled: bool) -> Dict[str, Any]:
|
|
7450
|
+
"""Toggle "mesh prendida" keep-alive for THIS meshwork (task baefc8ab,
|
|
7451
|
+
Samuel 2026-06-10: "quiero dejar la mesh prendida sin tasks — que se
|
|
7452
|
+
mantengan atentos a cualquier mensaje").
|
|
7453
|
+
|
|
7454
|
+
ON: the server sweep (mig 479) WAKES a stopped agent within ~5 min of an
|
|
7455
|
+
unread direct message arriving — sleep stays cheap (zero idle tokens) but
|
|
7456
|
+
the mesh answers. Explicit human Stops are still respected; agents offline
|
|
7457
|
+
>7 days are never resurrected. OFF (default): current behavior.
|
|
7458
|
+
|
|
7459
|
+
Owner or commander only — enforced server-side (mc_set_mesh_keep_alive).
|
|
7460
|
+
"""
|
|
7461
|
+
res = be.sb_rpc("mc_set_mesh_keep_alive", {
|
|
7462
|
+
"p_api_key": _get_api_key(),
|
|
7463
|
+
"p_project_id": _PROJECT_ID,
|
|
7464
|
+
"p_enabled": bool(enabled),
|
|
7465
|
+
})
|
|
7466
|
+
return res if isinstance(res, dict) else {"error": str(res)}
|
|
7467
|
+
|
|
7468
|
+
|
|
7439
7469
|
def inbox_resource() -> str:
|
|
7440
7470
|
"""Current pending messages for this agent. Read-only — does NOT mark as read."""
|
|
7441
7471
|
pending = be.sb_select(
|
|
@@ -56,6 +56,35 @@ _SLEEP_TEXT_MARKERS = (
|
|
|
56
56
|
# server + hook share authority on who counts as a human user.
|
|
57
57
|
_KNOWN_HUMAN_HANDLES = frozenset({"sammybenu", "samuel", "sam"})
|
|
58
58
|
|
|
59
|
+
# Task 3ec2cd4b (live repro 2026-06-10T00:34Z, msg 62062a6b): Samuel wrote
|
|
60
|
+
# "...que NO se vayan a dormir asi nomas... sabes?" — a COMPLAINT about agents
|
|
61
|
+
# sleeping, the opposite of an authorization — and the bare substring
|
|
62
|
+
# "a dormir" tripped must_exit on the commander mid-crisis. Human TEXT only
|
|
63
|
+
# authorizes sleep when it is directive-SHAPED; in doubt the message degrades
|
|
64
|
+
# to a normal msg (never must_exit). Three guards, human-text path only
|
|
65
|
+
# (structured payload.type/directive is untouched — that's the dashboard
|
|
66
|
+
# button / deliberate-agent path):
|
|
67
|
+
# LENGTH: a directive is short ("todos a dormir"); >80 chars is prose.
|
|
68
|
+
# QUESTION: "?"/"¿" = asking or complaining, not ordering.
|
|
69
|
+
# NEGATION: a negation token anywhere kills it ("que no se vayan a dormir").
|
|
70
|
+
_HUMAN_DIRECTIVE_MAX_CHARS = 80
|
|
71
|
+
_HUMAN_NEGATION_TOKENS = (
|
|
72
|
+
" no ", " not ", " nunca ", " jamas ", " jamás ", "que no", "sin que",
|
|
73
|
+
"don't", "do not", " ni ",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _human_text_is_directive(text: str) -> bool:
|
|
78
|
+
"""True when human text is shaped like an order, not prose about sleep."""
|
|
79
|
+
if len(text) > _HUMAN_DIRECTIVE_MAX_CHARS:
|
|
80
|
+
return False
|
|
81
|
+
if "?" in text or "\u00bf" in text:
|
|
82
|
+
return False
|
|
83
|
+
padded = " " + text.strip().lower() + " "
|
|
84
|
+
if any(tok in padded for tok in _HUMAN_NEGATION_TOKENS):
|
|
85
|
+
return False
|
|
86
|
+
return True
|
|
87
|
+
|
|
59
88
|
# ── ENJAMBRE G3 (task 3b0e4129) — swarm barrier events ──────────────────────
|
|
60
89
|
# The mc_swarm_barrier_check trigger (mig 471) fires when a swarm's tray
|
|
61
90
|
# drains. The runtime has NO raw Postgres connection (PostgREST + Realtime
|
|
@@ -121,7 +150,7 @@ def _looks_like_sleep_signal(m: Dict[str, Any]) -> bool:
|
|
|
121
150
|
return True
|
|
122
151
|
text = str(pl.get("text", "")).lower()
|
|
123
152
|
if text and any(marker in text for marker in _SLEEP_TEXT_MARKERS):
|
|
124
|
-
if _is_human_authored(m):
|
|
153
|
+
if _is_human_authored(m) and _human_text_is_directive(text):
|
|
125
154
|
return True
|
|
126
155
|
return False
|
|
127
156
|
|
|
@@ -158,3 +158,51 @@ class TestSplitMessages(unittest.TestCase):
|
|
|
158
158
|
|
|
159
159
|
if __name__ == "__main__":
|
|
160
160
|
unittest.main()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class TestCasualSpanishComplaintGuards(unittest.TestCase):
|
|
164
|
+
"""Task 3ec2cd4b — live repro 2026-06-10T00:34Z (msg 62062a6b): Samuel's
|
|
165
|
+
COMPLAINT about agents sleeping ("que NO se vayan a dormir asi nomas...")
|
|
166
|
+
tripped must_exit on the commander. Human text must be directive-shaped."""
|
|
167
|
+
|
|
168
|
+
REPRO = (
|
|
169
|
+
"oye ya me acague los agents se estan exiting and sleepign cuando no "
|
|
170
|
+
"tienen nada, que no se vayan a dormir asi nomas, porque aveces lo "
|
|
171
|
+
"querio dejar prendido sabes? que recomeindas, por ejemplo ahroita FE "
|
|
172
|
+
"no tenia nada asignadno y se fue a dormir , pero que si el suaurio "
|
|
173
|
+
"queria dejar su mesh rpendida sin taksks? sin que se fuerman que se "
|
|
174
|
+
"mantengan atentos a caulqueir mensaje"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def test_literal_live_repro_never_sleeps(self):
|
|
178
|
+
m = _msg(sender="sammybenu", payload={"text": self.REPRO})
|
|
179
|
+
self.assertFalse(_looks_like_sleep_signal(m))
|
|
180
|
+
split = _split_messages([m])
|
|
181
|
+
self.assertEqual(len(split["done_signals"]), 0)
|
|
182
|
+
self.assertEqual(len(split["messages"]), 1)
|
|
183
|
+
|
|
184
|
+
def test_negated_short_directive_blocked(self):
|
|
185
|
+
m = _msg(sender="sammybenu", payload={"text": "que no se vayan a dormir"})
|
|
186
|
+
self.assertFalse(_looks_like_sleep_signal(m))
|
|
187
|
+
|
|
188
|
+
def test_question_about_sleep_blocked(self):
|
|
189
|
+
m = _msg(sender="sammybenu", payload={"text": "se fueron a dormir?"})
|
|
190
|
+
self.assertFalse(_looks_like_sleep_signal(m))
|
|
191
|
+
|
|
192
|
+
def test_long_prose_with_marker_blocked(self):
|
|
193
|
+
m = _msg(sender="sammybenu", payload={
|
|
194
|
+
"text": "bueno pues al final creo que el plan de hoy salio bien y "
|
|
195
|
+
"ya cada quien puede irse a dormir cuando guste, mañana "
|
|
196
|
+
"seguimos con lo del deploy y lo de los archivos"})
|
|
197
|
+
self.assertFalse(_looks_like_sleep_signal(m))
|
|
198
|
+
|
|
199
|
+
def test_short_human_directive_still_authorizes(self):
|
|
200
|
+
for text in ("todos a dormir", "a dormir", "go to sleep", "sleep now"):
|
|
201
|
+
m = _msg(sender="sammybenu", payload={"text": text})
|
|
202
|
+
self.assertTrue(_looks_like_sleep_signal(m), text)
|
|
203
|
+
|
|
204
|
+
def test_structured_directive_untouched_by_guards(self):
|
|
205
|
+
# dashboard-button path: payload.type fires regardless of text shape
|
|
206
|
+
m = _msg(sender="mesh-commander",
|
|
207
|
+
payload={"type": "got_done", "text": self.REPRO})
|
|
208
|
+
self.assertTrue(_looks_like_sleep_signal(m))
|
|
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
|