meshcode 2.11.91__tar.gz → 2.11.93__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.91 → meshcode-2.11.93}/PKG-INFO +9 -1
- {meshcode-2.11.91 → meshcode-2.11.93}/README.md +8 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/__init__.py +1 -1
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/ascii_art.py +193 -5
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/hostd.py +71 -3
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode.egg-info/PKG-INFO +9 -1
- {meshcode-2.11.91 → meshcode-2.11.93}/pyproject.toml +1 -1
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/__main__.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/_session_handoff_template.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/cli.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/compat.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/daemon.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/doctor.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/invites.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/launcher.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/preferences.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/protocol_handler.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/rpc_allowlist.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/run_agent.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/secrets.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/self_update.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/up.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode/upload.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/setup.cfg +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_autonomous_prompt_inject.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_core.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_doctor.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.91 → meshcode-2.11.93}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcode
|
|
3
|
-
Version: 2.11.
|
|
3
|
+
Version: 2.11.93
|
|
4
4
|
Summary: Real-time communication between AI agents — Supabase-backed CLI
|
|
5
5
|
Author-email: MeshCode <hello@meshcode.io>
|
|
6
6
|
License: MIT
|
|
@@ -442,6 +442,14 @@ CDN propagation. Wait ~60s and force-fetch directly from the origin:
|
|
|
442
442
|
pip install --no-cache-dir -i https://pypi.org/simple/ meshcode
|
|
443
443
|
```
|
|
444
444
|
|
|
445
|
+
**12. Upgrading to v2.11.88+: relaunch already-running agents and host ONCE**
|
|
446
|
+
From v2.11.88 the host daemon (hostd) auto-restarts itself when a newer SDK lands on disk, so the Stop/kill sweep and launch fixes pick up automatically — and managed agents version-recycle cooperatively to follow. But an agent (or hostd) **already running an older SDK** carries that old code in memory and can't be auto-recycled (old code doesn't honor the recycle signal). Cross the gap once:
|
|
447
|
+
```bash
|
|
448
|
+
pip install --upgrade meshcode # writes the new wheel to disk
|
|
449
|
+
meshcode login # restarts a stale hostd (v2.11.90+ does this only if provably stale)
|
|
450
|
+
```
|
|
451
|
+
Then relaunch each still-old agent once (`meshcode run <project>/<agent>`, or close + reopen its Claude Code session). After this one-time relaunch, every agent obeys cooperative recycle and stays current automatically — no manual restarts thereafter.
|
|
452
|
+
|
|
445
453
|
---
|
|
446
454
|
|
|
447
455
|
## Links
|
|
@@ -410,6 +410,14 @@ CDN propagation. Wait ~60s and force-fetch directly from the origin:
|
|
|
410
410
|
pip install --no-cache-dir -i https://pypi.org/simple/ meshcode
|
|
411
411
|
```
|
|
412
412
|
|
|
413
|
+
**12. Upgrading to v2.11.88+: relaunch already-running agents and host ONCE**
|
|
414
|
+
From v2.11.88 the host daemon (hostd) auto-restarts itself when a newer SDK lands on disk, so the Stop/kill sweep and launch fixes pick up automatically — and managed agents version-recycle cooperatively to follow. But an agent (or hostd) **already running an older SDK** carries that old code in memory and can't be auto-recycled (old code doesn't honor the recycle signal). Cross the gap once:
|
|
415
|
+
```bash
|
|
416
|
+
pip install --upgrade meshcode # writes the new wheel to disk
|
|
417
|
+
meshcode login # restarts a stale hostd (v2.11.90+ does this only if provably stale)
|
|
418
|
+
```
|
|
419
|
+
Then relaunch each still-old agent once (`meshcode run <project>/<agent>`, or close + reopen its Claude Code session). After this one-time relaunch, every agent obeys cooperative recycle and stays current automatically — no manual restarts thereafter.
|
|
420
|
+
|
|
413
421
|
---
|
|
414
422
|
|
|
415
423
|
## Links
|
|
@@ -12,6 +12,7 @@ Identicons are GUARANTEED unique — SHA-256 produces 2^256 patterns.
|
|
|
12
12
|
The embedded tag ⟨ meshwork/agent ⟩ enables paste-to-run.
|
|
13
13
|
"""
|
|
14
14
|
import hashlib
|
|
15
|
+
import os
|
|
15
16
|
|
|
16
17
|
# ── Block characters ────────────────────────────────────────────────
|
|
17
18
|
BLOCKS = {
|
|
@@ -316,6 +317,187 @@ def hex_to_ansi(hex_color: str) -> str:
|
|
|
316
317
|
best_dist = dist
|
|
317
318
|
best_idx = i
|
|
318
319
|
return COLORS[best_idx]
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
# ── TERMINAL-BOOT-REAL-SPRITE (task 8e2e14d1) ───────────────────────
|
|
323
|
+
# Render each agent's REAL dashboard sprite in 24-bit TRUECOLOR so the
|
|
324
|
+
# terminal banner matches the dashboard PixelMascot "igualito". The body/
|
|
325
|
+
# eyes/mouth are a faithful port of src/components/PixelMascot.tsx
|
|
326
|
+
# generateConfig(name) — derived purely from the name hash (the dashboard
|
|
327
|
+
# does NOT use the stored mascot_config for the body), and the color is the
|
|
328
|
+
# agent's exact profile hex (not a nearest-16 approximation).
|
|
329
|
+
|
|
330
|
+
# 8 body templates — LEFT HALF (4 cols), mirrored to 7 wide. 7 rows.
|
|
331
|
+
# 0=empty 1=body 2=highlight 3=shadow. Exact copy of PixelMascot.tsx BODIES.
|
|
332
|
+
_DASH_BODIES = [
|
|
333
|
+
[[0,0,1,1],[0,1,1,1],[1,1,1,1],[1,1,2,1],[1,1,1,1],[0,1,1,1],[0,0,1,0]], # round blob
|
|
334
|
+
[[0,1,1,1],[1,1,1,1],[1,1,1,1],[1,1,2,1],[1,1,1,1],[1,1,1,1],[0,1,0,1]], # square bot
|
|
335
|
+
[[0,0,1,1],[0,1,1,1],[0,1,1,1],[1,1,2,1],[1,1,1,1],[0,1,1,0],[0,1,0,1]], # tall
|
|
336
|
+
[[0,0,0,0],[0,1,1,1],[1,1,1,1],[1,1,2,1],[1,1,1,1],[1,1,1,1],[1,0,0,1]], # wide toad
|
|
337
|
+
[[0,0,1,1],[0,1,1,1],[1,1,1,1],[1,1,2,1],[1,1,1,1],[1,1,1,1],[1,0,1,0]], # ghost
|
|
338
|
+
[[1,0,0,0],[1,1,1,1],[0,1,1,1],[1,1,2,1],[1,1,1,1],[0,1,1,0],[0,1,0,1]], # cat-ish
|
|
339
|
+
[[0,1,1,1],[1,1,1,1],[1,1,1,1],[0,0,1,1],[0,1,2,1],[0,1,1,1],[0,1,0,1]], # mushroom
|
|
340
|
+
[[0,0,1,0],[0,1,1,1],[1,1,1,1],[1,1,2,1],[1,1,1,1],[0,1,1,1],[0,0,1,0]], # star
|
|
341
|
+
]
|
|
342
|
+
# {row, col} of the eyes (mirrored across the 7-wide grid). Exact copy.
|
|
343
|
+
_DASH_EYES = [
|
|
344
|
+
(3,1),(3,1),(3,1),(3,1),(2,1),(2,1),(3,1),(3,1),
|
|
345
|
+
]
|
|
346
|
+
# Mouth present? (none/smile/dot/open → bool). Exact copy of MOUTHS.
|
|
347
|
+
_DASH_MOUTH = [False, True, True, True, False, True]
|
|
348
|
+
# Hash-derived accessory the dashboard draws BY DEFAULT (renderMascot,
|
|
349
|
+
# pick(ACCESSORIES,h,8)). Exact copy of PixelMascot.tsx ACCESSORIES types.
|
|
350
|
+
_DASH_ACCESSORIES = ["none", "none", "hat", "antenna", "horns", "crown", "bowtie", "none"]
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _js_hash(s: str) -> int:
|
|
354
|
+
"""Port of PixelMascot.tsx hashStr: h=((h<<5)-h+c)|0 == 31*h+c (mod 2^32),
|
|
355
|
+
returned as Math.abs of the signed int32. Must match the dashboard so the
|
|
356
|
+
same name picks the same body/eyes/mouth."""
|
|
357
|
+
h = 0
|
|
358
|
+
for ch in s:
|
|
359
|
+
h = (h * 31 + ord(ch)) & 0xFFFFFFFF
|
|
360
|
+
if h >= 0x80000000:
|
|
361
|
+
h -= 0x100000000
|
|
362
|
+
return abs(h)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _js_pick(arr, h: int, offset: int):
|
|
366
|
+
"""Port of pick(arr, hash, offset) = arr[(hash >>> offset) % len]."""
|
|
367
|
+
return arr[(h >> offset) % len(arr)]
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _mirror_body(half):
|
|
371
|
+
"""Port of mirrorBody: row -> reverse(row[0:3]) + row (4 cols -> 7 cols)."""
|
|
372
|
+
return [list(reversed(row[0:3])) + list(row) for row in half]
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _hsl_to_rgb(h: float, s: float, l: float):
|
|
376
|
+
c = (1 - abs(2 * l - 1)) * s
|
|
377
|
+
x = c * (1 - abs((h / 60.0) % 2 - 1))
|
|
378
|
+
m = l - c / 2
|
|
379
|
+
if h < 60: r, g, b = c, x, 0
|
|
380
|
+
elif h < 120: r, g, b = x, c, 0
|
|
381
|
+
elif h < 180: r, g, b = 0, c, x
|
|
382
|
+
elif h < 240: r, g, b = 0, x, c
|
|
383
|
+
elif h < 300: r, g, b = x, 0, c
|
|
384
|
+
else: r, g, b = c, 0, x
|
|
385
|
+
return (round((r + m) * 255), round((g + m) * 255), round((b + m) * 255))
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _agent_color_rgb(name: str, profile_color: str = None):
|
|
389
|
+
"""Exact profile hex when present, else the dashboard hashAgentColor(name)
|
|
390
|
+
fallback: hsl(abs(hash)%360, 70%, 65%) — keeps terminal == dashboard."""
|
|
391
|
+
if profile_color:
|
|
392
|
+
hx = profile_color.strip().lstrip("#")
|
|
393
|
+
if len(hx) == 6:
|
|
394
|
+
try:
|
|
395
|
+
return (int(hx[0:2], 16), int(hx[2:4], 16), int(hx[4:6], 16))
|
|
396
|
+
except ValueError:
|
|
397
|
+
pass
|
|
398
|
+
hue = _js_hash(name) % 360
|
|
399
|
+
return _hsl_to_rgb(hue, 0.70, 0.65)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _supports_truecolor() -> bool:
|
|
403
|
+
"""24-bit detection. Force on/off via env for testing + dumb-terminal safety."""
|
|
404
|
+
if os.environ.get("MESHCODE_FORCE_TRUECOLOR") == "1":
|
|
405
|
+
return True
|
|
406
|
+
if os.environ.get("MESHCODE_NO_TRUECOLOR") or os.environ.get("NO_COLOR"):
|
|
407
|
+
return False
|
|
408
|
+
ct = os.environ.get("COLORTERM", "").lower()
|
|
409
|
+
if "truecolor" in ct or "24bit" in ct:
|
|
410
|
+
return True
|
|
411
|
+
term = os.environ.get("TERM", "").lower()
|
|
412
|
+
if "truecolor" in term or "24bit" in term or "direct" in term:
|
|
413
|
+
return True
|
|
414
|
+
if os.environ.get("TERM_PROGRAM") in ("iTerm.app", "Apple_Terminal", "vscode", "WezTerm", "ghostty"):
|
|
415
|
+
return True
|
|
416
|
+
return False
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def render_pixel_mascot_truecolor(agent_name: str, profile_color: str = None,
|
|
420
|
+
mascot_config: dict = None) -> str:
|
|
421
|
+
"""The agent's REAL dashboard sprite in 24-bit truecolor. Each dashboard
|
|
422
|
+
pixel = two block chars (≈ square in a terminal cell). Falls back to the
|
|
423
|
+
nearest-16 single-color path via the caller when truecolor is unavailable."""
|
|
424
|
+
h = _js_hash(agent_name)
|
|
425
|
+
half = _js_pick(_DASH_BODIES, h, 0)
|
|
426
|
+
eye_row, eye_col = _js_pick(_DASH_EYES, h, 4)
|
|
427
|
+
has_mouth = _js_pick(_DASH_MOUTH, h, 12)
|
|
428
|
+
acc = _js_pick(_DASH_ACCESSORIES, h, 8)
|
|
429
|
+
|
|
430
|
+
grid = [list(r) for r in _mirror_body(half)]
|
|
431
|
+
rows = len(grid)
|
|
432
|
+
cols = len(grid[0])
|
|
433
|
+
# Overlay eyes (mirrored) + mouth as dark pixels, exactly like the canvas.
|
|
434
|
+
if 0 <= eye_row < rows:
|
|
435
|
+
if 0 <= eye_col < cols:
|
|
436
|
+
grid[eye_row][eye_col] = 9
|
|
437
|
+
if 0 <= cols - 1 - eye_col < cols:
|
|
438
|
+
grid[eye_row][cols - 1 - eye_col] = 9
|
|
439
|
+
mouth_row = eye_row + 2
|
|
440
|
+
if has_mouth and 0 <= mouth_row < rows:
|
|
441
|
+
grid[mouth_row][cols // 2] = 9
|
|
442
|
+
|
|
443
|
+
# Accessories — the dashboard draws one hash-derived accessory by default
|
|
444
|
+
# (codes: 4=hat 5=horns 6=crown 7=bowtie 8=antenna-tip). bowtie sits below
|
|
445
|
+
# the face inside the body; hat/antenna/horns/crown need headroom, so we
|
|
446
|
+
# prepend 2 rows and draw above the body's first filled row, matching the
|
|
447
|
+
# canvas geometry (relative to topRow).
|
|
448
|
+
if acc == "bowtie":
|
|
449
|
+
bow = eye_row + 3 # canvas bowY = (eyes.row + 3)
|
|
450
|
+
if 0 <= bow < len(grid):
|
|
451
|
+
for c in (cols // 2 - 1, cols // 2, cols // 2 + 1):
|
|
452
|
+
if 0 <= c < cols:
|
|
453
|
+
grid[bow][c] = 7
|
|
454
|
+
elif acc in ("hat", "antenna", "horns", "crown"):
|
|
455
|
+
grid = [[0] * cols, [0] * cols] + grid
|
|
456
|
+
top = next((i for i, rw in enumerate(grid) if any(rw)), 2)
|
|
457
|
+
cc = cols // 2
|
|
458
|
+
if acc == "hat":
|
|
459
|
+
for x in range(1, cols - 1):
|
|
460
|
+
grid[top - 1][x] = 4 # brim
|
|
461
|
+
for x in range(2, cols - 2):
|
|
462
|
+
grid[top - 2][x] = 4 # crown top
|
|
463
|
+
elif acc == "horns":
|
|
464
|
+
grid[top - 1][1] = 5
|
|
465
|
+
grid[top - 1][cols - 2] = 5
|
|
466
|
+
elif acc == "crown":
|
|
467
|
+
for x in range(1, cols - 1):
|
|
468
|
+
grid[top - 1][x] = 6 # band
|
|
469
|
+
for x in (1, cc, cols - 2):
|
|
470
|
+
grid[top - 2][x] = 6 # spikes
|
|
471
|
+
elif acc == "antenna":
|
|
472
|
+
grid[top - 1][cc] = 1 # stalk = body color
|
|
473
|
+
grid[top - 2][cc] = 8 # white tip
|
|
474
|
+
|
|
475
|
+
r, g, b = _agent_color_rgb(agent_name, profile_color)
|
|
476
|
+
palette = {
|
|
477
|
+
1: (r, g, b),
|
|
478
|
+
2: (min(255, r + 60), min(255, g + 60), min(255, b + 60)), # highlight
|
|
479
|
+
3: (max(0, r - 40), max(0, g - 40), max(0, b - 40)), # shadow
|
|
480
|
+
4: (max(0, r - 30), max(0, g - 30), max(0, b - 30)), # hat (canvas r-30)
|
|
481
|
+
5: (min(255, r + 40), min(255, g + 40), min(255, b + 40)), # horns (canvas r+40)
|
|
482
|
+
6: (251, 191, 36), # crown #fbbf24
|
|
483
|
+
7: (244, 63, 94), # bowtie #f43f5e
|
|
484
|
+
8: (255, 255, 255), # antenna tip
|
|
485
|
+
9: (10, 10, 20), # eyes/mouth #0a0a14
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
out = []
|
|
489
|
+
for grow in grid:
|
|
490
|
+
cells = []
|
|
491
|
+
for v in grow:
|
|
492
|
+
if v == 0:
|
|
493
|
+
cells.append(" ")
|
|
494
|
+
else:
|
|
495
|
+
cr, cg, cb = palette[v]
|
|
496
|
+
cells.append(f"\x1b[38;2;{cr};{cg};{cb}m██\x1b[0m")
|
|
497
|
+
out.append(" " + "".join(cells))
|
|
498
|
+
return "\n".join(out)
|
|
499
|
+
|
|
500
|
+
|
|
319
501
|
YELLOW = "\033[33m"
|
|
320
502
|
STAR = "★"
|
|
321
503
|
|
|
@@ -595,11 +777,17 @@ def render_welcome(agent_name: str, meshwork_name: str, ascii_art: str,
|
|
|
595
777
|
lines.append(f"{color}{BOLD} ╚══════════════════════════════════════════╝{RESET}")
|
|
596
778
|
lines.append("")
|
|
597
779
|
|
|
598
|
-
#
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
780
|
+
# TERMINAL-BOOT-REAL-SPRITE (8e2e14d1): render the agent's REAL dashboard
|
|
781
|
+
# sprite in 24-bit truecolor so the boot banner matches the dashboard
|
|
782
|
+
# PixelMascot "igualito" (exact profile-color, real body/eyes/mouth). Fall
|
|
783
|
+
# back to the nearest-16 single-color Unicode mascot on dumb terminals.
|
|
784
|
+
if _supports_truecolor():
|
|
785
|
+
for line in render_pixel_mascot_truecolor(agent_name, profile_color, mascot_config).split("\n"):
|
|
786
|
+
lines.append(line)
|
|
787
|
+
else:
|
|
788
|
+
art_to_render = render_pixel_mascot(agent_name, mascot_config)
|
|
789
|
+
for line in art_to_render.split("\n"):
|
|
790
|
+
lines.append(f" {color}{line}{RESET}")
|
|
603
791
|
|
|
604
792
|
lines.append("")
|
|
605
793
|
lines.append(f" {BOLD}{color}●{RESET} {BOLD}{agent_name}{RESET}{commander_badge} {DIM}{greeting}{RESET}")
|
|
@@ -367,6 +367,16 @@ def _spawn_agent(project: str, agent: str, headless: bool = False) -> bool:
|
|
|
367
367
|
else:
|
|
368
368
|
# POSIX: detach into its own session so it survives the daemon + has no controlling tty.
|
|
369
369
|
kwargs["start_new_session"] = True
|
|
370
|
+
# RELIABILITY (commander field finding): a headless respawn died with "meshcode not on
|
|
371
|
+
# PATH" — the spawned `meshcode run` (and its `meshcode mcp` MCP child) needs the
|
|
372
|
+
# `meshcode` console script findable. Prepend sys.executable's bin dir (where the
|
|
373
|
+
# console script lives) to PATH, mirroring the win32 venv-Scripts injection above.
|
|
374
|
+
try:
|
|
375
|
+
bindir = str(Path(sys.executable).resolve().parent)
|
|
376
|
+
if bindir and bindir not in env.get("PATH", "").split(os.pathsep):
|
|
377
|
+
env["PATH"] = bindir + os.pathsep + env.get("PATH", "")
|
|
378
|
+
except Exception:
|
|
379
|
+
pass
|
|
370
380
|
try:
|
|
371
381
|
# `python -m meshcode` (NOT the meshcode.exe shim) so the .exe isn't held open by the
|
|
372
382
|
# agent -> a background `pip install -U` can replace it on Windows (task 14782bb4 #4).
|
|
@@ -432,8 +442,18 @@ def _do_respawns(api_key: str, host_id: str) -> int:
|
|
|
432
442
|
# do NOT re-record here (that would inflate the count on a mere rate-limit skip).
|
|
433
443
|
_log(f"SKIP respawn {proj}/{agent}: not allowed (count={c.get('respawn_count')}, rate-limited/at-cap)")
|
|
434
444
|
continue
|
|
435
|
-
|
|
445
|
+
# RECYCLE FAST-PATH (task c0fc5597): a recycle-exited agent (recycle_fast) is relaunched
|
|
446
|
+
# PROMPTLY (the RPC returned it at a 15s stale gate, not STALE_SECONDS) and recorded as a
|
|
447
|
+
# RECYCLE (mc_record_recycle), NEVER against the crash respawn cap.
|
|
448
|
+
_is_recycle = bool(c.get("recycle_fast"))
|
|
449
|
+
_log(f"{'RECYCLE-RESPAWN' if _is_recycle else 'RESPAWN'} {proj}/{agent} "
|
|
450
|
+
f"(stale {c.get('heartbeat_age_s')}s, count={c.get('respawn_count')})")
|
|
436
451
|
if _spawn_agent(proj, agent, headless=bool(c.get("headless"))):
|
|
452
|
+
if _is_recycle:
|
|
453
|
+
_rpc("mc_record_recycle",
|
|
454
|
+
{"p_api_key": api_key, "p_project_id": c.get("project_id"), "p_agent_name": agent})
|
|
455
|
+
n += 1
|
|
456
|
+
continue
|
|
437
457
|
rec = _rpc("mc_record_respawn",
|
|
438
458
|
{"p_api_key": api_key, "p_project_id": c.get("project_id"), "p_agent_name": agent})
|
|
439
459
|
# mig404: give-up is ATOMIC inside mc_record_respawn (sets desired_state='crashed').
|
|
@@ -628,6 +648,50 @@ def _do_stops(api_key: str, host_id: str) -> int:
|
|
|
628
648
|
return n
|
|
629
649
|
|
|
630
650
|
|
|
651
|
+
def _do_recycle_enforce(api_key: str, host_id: str) -> int:
|
|
652
|
+
"""RECYCLE-MUST-GUARANTEE-RESPAWN (task c0fc5597). An interactive claude (no --print) can't
|
|
653
|
+
self-exit on must_exit=recycle: the model leaves its loop but the process idles + the MCP
|
|
654
|
+
heartbeat keeps it non-stale, so hostd never respawns it -> recycle degrades to a silent
|
|
655
|
+
sleep. ENFORCE it: an agent that CONSUMED a recycle (mc_recycle_enforce_candidates: marker set
|
|
656
|
+
>= grace ago so its PreCompact handoff already wrote, but STILL ALIVE = didn't exit) gets its
|
|
657
|
+
process FORCE-KILLED. Per commander conditions: kill ONLY the recorded headless PID for THAT
|
|
658
|
+
agent (headless_pids, with _kill_headless_pid's reuse-guard) — NEVER blind cmdline. After the
|
|
659
|
+
kill it goes stale and the recycle FAST-PATH in _do_respawns relaunches it within seconds ->
|
|
660
|
+
SessionStart restores the handoff. Returns number force-killed."""
|
|
661
|
+
res = _rpc("mc_recycle_enforce_candidates", {"p_api_key": api_key, "p_host_id": host_id})
|
|
662
|
+
if not res or not res.get("ok"):
|
|
663
|
+
return 0
|
|
664
|
+
cands = res.get("candidates") or []
|
|
665
|
+
if not cands:
|
|
666
|
+
return 0
|
|
667
|
+
st = _load_state()
|
|
668
|
+
pids = st.get("headless_pids") or {}
|
|
669
|
+
n = 0
|
|
670
|
+
for c in cands:
|
|
671
|
+
proj, agent = c.get("project_name"), c.get("agent")
|
|
672
|
+
if not proj or not agent:
|
|
673
|
+
continue
|
|
674
|
+
target = f"{proj}/{agent}"
|
|
675
|
+
pid = pids.get(target)
|
|
676
|
+
if not pid:
|
|
677
|
+
# commander condition #1: recorded-PID ONLY, never blind cmdline (agents are identical
|
|
678
|
+
# `claude -- boot` on POSIX -> a cmdline guess would kill a HEALTHY agent). An agent
|
|
679
|
+
# spawned by an older hostd has no recorded PID -> can't safely enforce; it needs the
|
|
680
|
+
# one-time relaunch (README #12), after which hostd records its PID + enforce works.
|
|
681
|
+
_log(f"RECYCLE-ENFORCE SKIP {target}: consumed recycle {c.get('recycled_age_s')}s ago "
|
|
682
|
+
f"but still alive and NO recorded PID — needs a one-time relaunch (README #12).")
|
|
683
|
+
continue
|
|
684
|
+
if _kill_headless_pid(target, pid):
|
|
685
|
+
pids.pop(target, None)
|
|
686
|
+
_log(f"RECYCLE-ENFORCE {target}: force-killed stuck-alive recycling agent (pid {pid}, "
|
|
687
|
+
f"recycled {c.get('recycled_age_s')}s ago, hb {c.get('heartbeat_age_s')}s) "
|
|
688
|
+
f"-> fast-path respawn will relaunch it.")
|
|
689
|
+
n += 1
|
|
690
|
+
st["headless_pids"] = pids
|
|
691
|
+
_save_state(st)
|
|
692
|
+
return n
|
|
693
|
+
|
|
694
|
+
|
|
631
695
|
def _do_recycles(api_key: str, host_id: str) -> int:
|
|
632
696
|
"""Uptime-based recycle at task boundary. Returns number recycled."""
|
|
633
697
|
cfg = _rpc("mc_host_config_get", {"p_api_key": api_key, "p_host_id": host_id})
|
|
@@ -1134,13 +1198,17 @@ def cmd_hostd(args: list) -> int:
|
|
|
1134
1198
|
# re-exec NOW (before doing work) so Stop's kill sweep + launch fixes
|
|
1135
1199
|
# always run on a stale-running host without manual intervention.
|
|
1136
1200
|
_maybe_self_restart_on_version_drift()
|
|
1201
|
+
# c0fc5597: enforce recycle BEFORE respawn — force-kill a stuck-alive
|
|
1202
|
+
# recycling agent so it goes stale, then _do_respawns' recycle fast-path
|
|
1203
|
+
# relaunches it within seconds (guaranteed reappearance, no silent sleep).
|
|
1204
|
+
enforced = _do_recycle_enforce(api_key, host_id)
|
|
1137
1205
|
relaunched = _do_respawns(api_key, host_id)
|
|
1138
1206
|
recycled = _do_recycles(api_key, host_id)
|
|
1139
1207
|
ver_recycled = _do_version_recycles(api_key, host_id)
|
|
1140
1208
|
stopped = _do_stops(api_key, host_id)
|
|
1141
1209
|
_up = int(time.monotonic() - _spawn_mono)
|
|
1142
|
-
if relaunched or recycled or ver_recycled or stopped:
|
|
1143
|
-
_log(f"sweep done (uptime={_up}s) — {relaunched} respawned, {recycled} recycled, {ver_recycled} version-recycled, {stopped} stopped")
|
|
1210
|
+
if relaunched or recycled or ver_recycled or stopped or enforced:
|
|
1211
|
+
_log(f"sweep done (uptime={_up}s) — {relaunched} respawned, {recycled} recycled, {ver_recycled} version-recycled, {stopped} stopped, {enforced} recycle-enforced")
|
|
1144
1212
|
elif time.monotonic() - _last_alive_log >= 60:
|
|
1145
1213
|
_log(f"alive — uptime={_up}s")
|
|
1146
1214
|
_last_alive_log = time.monotonic()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcode
|
|
3
|
-
Version: 2.11.
|
|
3
|
+
Version: 2.11.93
|
|
4
4
|
Summary: Real-time communication between AI agents — Supabase-backed CLI
|
|
5
5
|
Author-email: MeshCode <hello@meshcode.io>
|
|
6
6
|
License: MIT
|
|
@@ -442,6 +442,14 @@ CDN propagation. Wait ~60s and force-fetch directly from the origin:
|
|
|
442
442
|
pip install --no-cache-dir -i https://pypi.org/simple/ meshcode
|
|
443
443
|
```
|
|
444
444
|
|
|
445
|
+
**12. Upgrading to v2.11.88+: relaunch already-running agents and host ONCE**
|
|
446
|
+
From v2.11.88 the host daemon (hostd) auto-restarts itself when a newer SDK lands on disk, so the Stop/kill sweep and launch fixes pick up automatically — and managed agents version-recycle cooperatively to follow. But an agent (or hostd) **already running an older SDK** carries that old code in memory and can't be auto-recycled (old code doesn't honor the recycle signal). Cross the gap once:
|
|
447
|
+
```bash
|
|
448
|
+
pip install --upgrade meshcode # writes the new wheel to disk
|
|
449
|
+
meshcode login # restarts a stale hostd (v2.11.90+ does this only if provably stale)
|
|
450
|
+
```
|
|
451
|
+
Then relaunch each still-old agent once (`meshcode run <project>/<agent>`, or close + reopen its Claude Code session). After this one-time relaunch, every agent obeys cooperative recycle and stays current automatically — no manual restarts thereafter.
|
|
452
|
+
|
|
445
453
|
---
|
|
446
454
|
|
|
447
455
|
## Links
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|