meshcode 2.11.150__tar.gz → 2.11.153__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.
Files changed (107) hide show
  1. {meshcode-2.11.150 → meshcode-2.11.153}/PKG-INFO +1 -1
  2. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/__init__.py +1 -1
  3. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/comms_v4.py +41 -3
  4. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/doctor.py +101 -1
  5. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/protocol_handler.py +60 -27
  6. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/run_agent.py +24 -1
  7. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode.egg-info/PKG-INFO +1 -1
  8. {meshcode-2.11.150 → meshcode-2.11.153}/pyproject.toml +1 -1
  9. {meshcode-2.11.150 → meshcode-2.11.153}/README.md +0 -0
  10. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/__main__.py +0 -0
  11. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/_session_handoff_template.py +0 -0
  12. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/_stop_hook_template.py +0 -0
  13. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/ascii_art.py +0 -0
  14. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/atomic_push.py +0 -0
  15. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/claude_update.py +0 -0
  16. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/cli.py +0 -0
  17. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/compat.py +0 -0
  18. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/daemon.py +0 -0
  19. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/date_parse.py +0 -0
  20. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/error_hints.py +0 -0
  21. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/exceptions.py +0 -0
  22. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/helper_visuals.py +0 -0
  23. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/hooks/__init__.py +0 -0
  24. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/hooks/repo_path_lock.py +0 -0
  25. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/hostd.py +0 -0
  26. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/invites.py +0 -0
  27. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/launcher.py +0 -0
  28. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/launcher_install.py +0 -0
  29. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/__init__.py +0 -0
  30. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/__main__.py +0 -0
  31. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/backend.py +0 -0
  32. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/realtime.py +0 -0
  33. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/server.py +0 -0
  34. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
  35. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/swarm.py +0 -0
  36. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/test_backend.py +0 -0
  37. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
  38. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
  39. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
  40. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  41. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  42. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/test_swarm.py +0 -0
  43. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/preferences.py +0 -0
  44. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/protocol_v2.py +0 -0
  45. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/quickstart.py +0 -0
  46. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/rpc_allowlist.py +0 -0
  47. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/scripts/check_secrets.py +0 -0
  48. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/scripts/race_rate_harness.py +0 -0
  49. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/secrets.py +0 -0
  50. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/self_update.py +0 -0
  51. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/setup_clients.py +0 -0
  52. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/supervisor.py +0 -0
  53. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/up.py +0 -0
  54. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/upload.py +0 -0
  55. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode.egg-info/SOURCES.txt +0 -0
  56. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode.egg-info/dependency_links.txt +0 -0
  57. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode.egg-info/entry_points.txt +0 -0
  58. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode.egg-info/requires.txt +0 -0
  59. {meshcode-2.11.150 → meshcode-2.11.153}/meshcode.egg-info/top_level.txt +0 -0
  60. {meshcode-2.11.150 → meshcode-2.11.153}/setup.cfg +0 -0
  61. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_auto_update_hardening.py +0 -0
  62. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_autonomous_closegap_1.py +0 -0
  63. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_autonomous_closegap_2.py +0 -0
  64. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_autonomous_closegap_3.py +0 -0
  65. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_autonomous_prompt_inject.py +0 -0
  66. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_boot_bug_regression.py +0 -0
  67. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_color_truecolor.py +0 -0
  68. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_core.py +0 -0
  69. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_cross_agent_messaging.py +0 -0
  70. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_date_parse.py +0 -0
  71. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_doctor.py +0 -0
  72. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_epistemic_v1_python_sdk.py +0 -0
  73. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_epistemic_v1_stop_conditions.py +0 -0
  74. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_esc_deaf_state.py +0 -0
  75. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_exceptions.py +0 -0
  76. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_file_upload.py +0 -0
  77. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_helper_visuals.py +0 -0
  78. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_hostd_launch_pinned_env.py +0 -0
  79. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_hostd_serve_discovery_split.py +0 -0
  80. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_hostd_zombie_sessions.py +0 -0
  81. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_init_device_code.py +0 -0
  82. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_install_guard.py +0 -0
  83. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_lease_sigterm_release.py +0 -0
  84. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_live_mesh_guard.py +0 -0
  85. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_mark_read_batch.py +0 -0
  86. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_marketplace_ratings.py +0 -0
  87. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_migration_integrity.py +0 -0
  88. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_pretrust_claude.py +0 -0
  89. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_realtime_event_freshness.py +0 -0
  90. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_rls_cross_tenant.py +0 -0
  91. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_rpc_grants.py +0 -0
  92. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_rpc_migrations.py +0 -0
  93. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_run_agent_dry_run.py +0 -0
  94. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_run_agent_no_server_import.py +0 -0
  95. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_security_regressions.py +0 -0
  96. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_self_update_user_site.py +0 -0
  97. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_sentinel.py +0 -0
  98. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_session_replay_gate.py +0 -0
  99. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_setup_path.py +0 -0
  100. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_sleep_signals.py +0 -0
  101. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_status_enum_coverage.py +0 -0
  102. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_stay_on_loop_hook.py +0 -0
  103. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_stop_ghost_terminal.py +0 -0
  104. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_swarm_events.py +0 -0
  105. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_task_progress.py +0 -0
  106. {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_terminal_lifecycle.py +0 -0
  107. {meshcode-2.11.150 → meshcode-2.11.153}/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.150
3
+ Version: 2.11.153
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -1,5 +1,5 @@
1
1
  """MeshCode — Real-time communication between AI agents."""
2
- __version__ = "2.11.150"
2
+ __version__ = "2.11.153"
3
3
 
4
4
  # Exception hierarchy — eagerly imported (lightweight, no deps)
5
5
  from meshcode.exceptions import ( # noqa: F401
@@ -319,8 +319,28 @@ def get_project_id(project_name):
319
319
  if rows:
320
320
  return rows[0]["id"]
321
321
 
322
- # Last resort: try to create. Will fail under RLS for unauthenticated
323
- # contexts, but kept for legacy callers + admin tooling.
322
+ # ZERO-FRICTION onboarding (R3): the project doesn't exist yet create it
323
+ # for the authenticated user via the same RPC the MCP tool uses, then
324
+ # re-resolve to get its id. Makes `meshcode go <agent> --project <new>`
325
+ # just work right after `meshcode init` (no dashboard round-trip).
326
+ if api_key:
327
+ try:
328
+ cr = sb_rpc("mc_create_meshwork_by_api_key", {
329
+ "p_api_key": api_key, "p_name": project_name,
330
+ })
331
+ if isinstance(cr, dict) and (cr.get("ok") or cr.get("project_id")):
332
+ r2 = sb_rpc("mc_resolve_project", {
333
+ "p_api_key": api_key, "p_project_name": project_name,
334
+ })
335
+ if isinstance(r2, dict) and r2.get("project_id"):
336
+ return r2["project_id"]
337
+ if isinstance(cr.get("project_id"), str):
338
+ return cr["project_id"]
339
+ except Exception:
340
+ pass
341
+
342
+ # Last resort: legacy anon insert (fails under RLS for owned projects;
343
+ # kept for very old test scripts / admin tooling).
324
344
  result = sb_insert("mc_projects", {"name": project_name})
325
345
  if result and len(result) > 0:
326
346
  return result[0]["id"]
@@ -2426,6 +2446,14 @@ def login(api_key):
2426
2446
  except Exception:
2427
2447
  pass
2428
2448
 
2449
+ # ZERO-FRICTION onboarding (R1): auto-install Claude Code so `pip install
2450
+ # meshcode` + `meshcode login` is enough — no separate manual editor install.
2451
+ try:
2452
+ from meshcode.doctor import ensure_claude_installed
2453
+ ensure_claude_installed(verbose=True)
2454
+ except Exception:
2455
+ pass
2456
+
2429
2457
  return True
2430
2458
 
2431
2459
 
@@ -2822,7 +2850,8 @@ if __name__ == "__main__":
2822
2850
  if not _has_creds:
2823
2851
  print()
2824
2852
  print(" Welcome to MeshCode!")
2825
- print(" First time? Run: meshcode quickstart")
2853
+ print(" First time? Run: meshcode init")
2854
+ print(" ↳ opens your browser to approve — no API key to copy/paste")
2826
2855
  print(" Already have an API key? Run: meshcode login <api_key>")
2827
2856
  print(" Need help? Run: meshcode doctor")
2828
2857
  print()
@@ -2914,6 +2943,15 @@ if __name__ == "__main__":
2914
2943
  print(" " + "=" * 40)
2915
2944
  print()
2916
2945
 
2946
+ # ZERO-FRICTION onboarding (R1): auto-install Claude Code BEFORE the
2947
+ # pre-flight so a brand-new machine (no claude) self-heals and PASSES
2948
+ # doctor instead of hard-blocking onboarding.
2949
+ try:
2950
+ from meshcode.doctor import ensure_claude_installed
2951
+ ensure_claude_installed(verbose=True)
2952
+ except Exception:
2953
+ pass
2954
+
2917
2955
  # B2 pre-flight: refuse to onboard if env is broken
2918
2956
  from meshcode.doctor import run_doctor
2919
2957
  if run_doctor(skip_api_key_check=True) != 0:
@@ -17,7 +17,7 @@ import shutil
17
17
  import subprocess
18
18
  import urllib.request
19
19
  import urllib.error
20
- from typing import List, Tuple
20
+ from typing import List, Optional, Tuple
21
21
 
22
22
  Status = Tuple[str, str, str]
23
23
 
@@ -97,6 +97,106 @@ def _check_claude() -> Status:
97
97
  )
98
98
 
99
99
 
100
+ def _resolve_claude() -> Optional[str]:
101
+ """Return a path to the `claude` binary — via PATH, or common global-bin
102
+ locations a fresh `npm i -g` / claude.ai install lands in but that aren't
103
+ on this process's PATH yet (qa cold-start finding: macOS/Linux had no
104
+ fallback, only Windows did). None if not found."""
105
+ p = shutil.which("claude")
106
+ if p:
107
+ return p
108
+ if sys.platform == "win32":
109
+ appdata = os.environ.get("APPDATA", "")
110
+ local = os.environ.get("LOCALAPPDATA", "")
111
+ cands = [
112
+ os.path.join(appdata, "npm", "claude.cmd"),
113
+ os.path.join(appdata, "npm", "claude"),
114
+ os.path.join(local, "Programs", "claude", "claude.exe"),
115
+ ]
116
+ else:
117
+ home = os.path.expanduser("~")
118
+ cands = [
119
+ os.path.join(home, ".local", "bin", "claude"),
120
+ "/opt/homebrew/bin/claude",
121
+ "/usr/local/bin/claude",
122
+ os.path.join(home, ".npm-global", "bin", "claude"),
123
+ ]
124
+ for c in cands:
125
+ if c and os.path.isfile(c):
126
+ return c
127
+ # Ask npm where global packages live and check its bin dir.
128
+ try:
129
+ if sys.platform == "win32":
130
+ base = os.path.dirname(subprocess.check_output(
131
+ ["npm", "root", "-g"], text=True, timeout=5).strip())
132
+ names = ("claude.cmd", "claude.exe", "claude")
133
+ else:
134
+ base = os.path.join(subprocess.check_output(
135
+ ["npm", "prefix", "-g"], text=True, timeout=5).strip(), "bin")
136
+ names = ("claude",)
137
+ for n in names:
138
+ cp = os.path.join(base, n)
139
+ if os.path.isfile(cp):
140
+ return cp
141
+ except Exception:
142
+ pass
143
+ return None
144
+
145
+
146
+ def ensure_claude_installed(verbose: bool = False) -> bool:
147
+ """ZERO-FRICTION onboarding (R1): best-effort auto-install of the Claude
148
+ Code CLI so getting started is just `pip install meshcode` + `meshcode
149
+ init` / `meshcode login` — no separate manual editor install. Mirrors the
150
+ hostd._hostd_bootstrap pattern (best-effort, idempotent, NEVER raises —
151
+ auth/onboarding must not fail on a hiccup). Cross-platform: macOS / Linux /
152
+ Windows (qa finding — cover mac/linux so a cold machine doesn't break).
153
+
154
+ Returns True if `claude` is present (already, or after a successful
155
+ install), False if it's still missing (caller falls back to the manual
156
+ hint / doctor still reports it).
157
+ """
158
+ if _resolve_claude():
159
+ return True
160
+ npm = shutil.which("npm")
161
+ if npm:
162
+ if verbose:
163
+ print("[meshcode] Claude Code not found — installing "
164
+ "(npm i -g @anthropic-ai/claude-code)...")
165
+ try:
166
+ r = subprocess.run(
167
+ [npm, "install", "-g", "@anthropic-ai/claude-code"],
168
+ capture_output=True, text=True, timeout=300,
169
+ )
170
+ found = _resolve_claude()
171
+ if found:
172
+ # Make the freshly-installed claude visible to the REST of this
173
+ # process (the login/init flow) even if npm's global bin isn't
174
+ # on PATH yet. The later `meshcode run` is a separate process and
175
+ # resolves independently via _detect_editor's own fallback.
176
+ try:
177
+ bdir = os.path.dirname(found)
178
+ parts = os.environ.get("PATH", "").split(os.pathsep)
179
+ if bdir and bdir not in parts:
180
+ os.environ["PATH"] = bdir + os.pathsep + os.environ.get("PATH", "")
181
+ except Exception:
182
+ pass
183
+ if verbose:
184
+ print("[meshcode] Claude Code installed.")
185
+ return True
186
+ if verbose:
187
+ tail = (r.stderr or r.stdout or "").strip().splitlines()
188
+ print("[meshcode] Claude Code install did not complete"
189
+ + (f": {tail[-1]}" if tail else "."))
190
+ except Exception as e:
191
+ if verbose:
192
+ print(f"[meshcode] Claude Code auto-install skipped: {e}")
193
+ if verbose:
194
+ print("[meshcode] Claude Code is required. Install it with one of:")
195
+ print("[meshcode] npm i -g @anthropic-ai/claude-code (needs Node.js)")
196
+ print("[meshcode] or visit https://claude.ai/install")
197
+ return False
198
+
199
+
100
200
  def _check_node() -> Status:
101
201
  needs_node = False
102
202
  try:
@@ -272,30 +272,53 @@ def _tmux(tmux: str, *args: str) -> subprocess.CompletedProcess:
272
272
  return subprocess.run([tmux, *args], capture_output=True, text=True, timeout=15)
273
273
 
274
274
 
275
- def _fleet_wrap(cmd: str) -> str:
275
+ def _fleet_wrap(cmd: str, tmux: str = "tmux") -> str:
276
276
  """Shell line for an agent inside its tmux window.
277
277
 
278
- Drop the LAST `exec ` (same trick as the .command launcher) so the shell
279
- survives the agent's exit and can translate rc 143 (SIGTERM = hostd
280
- stop/ghost-kill, task 91201315: that's the job FINISHING) to 0 otherwise
281
- `remain-on-exit failed` would keep a dead pane for every plain Stop.
282
-
283
- CLOSE-TAB=STOP (task 4df5a126, Samuel 68b1c17b: "cierro su terminal y
284
- ya"): export MESHCODE_CLOSE_STOP_SIGHUP=1 so the MCP server's gated POSIX
285
- close=stop handler (b6da0d54, default OFF) arms INSIDE fleet tabs only.
286
- In a tmux pane SIGHUP is unambiguous only kill-window / kill-session /
287
- tmux-server death close the pty (a window-close of the shared Terminal
288
- just DETACHES; ESC never closes the pty) so the false-stop risk that
289
- kept the flag off for plain windows (task dfa17461) does not exist here.
290
- Right-click a tab -> Kill => SIGHUP => agent flips desired_state=stopped
291
- server-side and exits clean: offline, NO hostd ghost-respawn (mig 494
292
- coherent). A real crash never runs the handler => crash-respawn intact."""
278
+ RAII / opener-owns-teardown (Samuel P0 'vergo de terminales' professional
279
+ supervisor pattern, NOT a patch): this wrapper OWNS its tmux window and an
280
+ EXIT+signal trap closes THAT window whenever the agent process diesclean
281
+ stop, recycle-exit, OR crash so a dead pane never lingers and accumulates
282
+ (459 spawns vs 0 closes / 18 days -> ~50 stale tabs). It self-targets via
283
+ $TMUX_PANE, so it can only EVER close its OWN window — never a live sibling's
284
+ (the 'never close a live agent' guardrail is structural here: the trap fires
285
+ only when THIS agent already exited). Replaces the old
286
+ `remain-on-exit=failed keeps the dead pane for debugging` behavior, which was
287
+ the accumulation source. rc 143 (SIGTERM = clean hostd stop, task 91201315)
288
+ and clean exits close silently; a crash rc is logged to stderr first.
289
+
290
+ Kill-switch env MESHCODE_FLEET_WINDOW_CLOSE: `0`/`off` restores the old
291
+ keep-dead-tab-for-debugging behavior; `dry`/`dryrun` logs intent without
292
+ closing.
293
+
294
+ Drops the LAST `exec ` (same trick as the .command launcher) so the shell
295
+ SURVIVES the agent's exit and the trap can run. CLOSE-TAB=STOP (task
296
+ 4df5a126, Samuel 68b1c17b): export MESHCODE_CLOSE_STOP_SIGHUP=1 so the MCP
297
+ server's gated POSIX close=stop handler arms INSIDE fleet tabs (right-click
298
+ tab -> Kill => SIGHUP => agent flips desired_state=stopped + exits clean, no
299
+ hostd ghost-respawn). A real crash still respawns (hostd, DB-driven) —
300
+ closing the crashed tab just means the respawn opens a fresh one."""
293
301
  if "exec " in cmd:
294
302
  head, _, tail = cmd.rpartition("exec ")
295
303
  cmd = head + tail
296
- return (f'export MESHCODE_CLOSE_STOP_SIGHUP=1; '
297
- f'{cmd}; MC_RC=$?; if [ "$MC_RC" = "143" ]; then exit 0; fi; '
298
- f'echo "[meshcode] agent exited rc=$MC_RC — tab kept for debugging"; exit $MC_RC')
304
+ tq = shlex.quote(tmux)
305
+ return (
306
+ 'export MESHCODE_CLOSE_STOP_SIGHUP=1; '
307
+ # RAII teardown: close OUR OWN tmux window on ANY exit/signal. $TMUX_PANE
308
+ # is set by tmux for the pane we run in, so kill-window can only ever hit
309
+ # THIS window. Disarm traps first (avoid double-run via the EXIT trap).
310
+ '_mc_close() { _rc=$?; trap - EXIT INT TERM HUP; '
311
+ 'case "${MESHCODE_FLEET_WINDOW_CLOSE:-}" in '
312
+ '0|off|false|no) exit "$_rc";; '
313
+ 'dry|dryrun|dry-run) echo "[meshcode] DRY-RUN: would close own tab (rc=$_rc)" >&2; exit "$_rc";; '
314
+ 'esac; '
315
+ '{ [ "$_rc" != "0" ] && [ "$_rc" != "143" ]; } && '
316
+ 'echo "[meshcode] agent exited rc=$_rc — closing tab (1 agent = 1 terminal)" >&2; '
317
+ f'{tq} kill-window -t "${{TMUX_PANE:-}}" 2>/dev/null; '
318
+ 'exit "$_rc"; }; '
319
+ 'trap _mc_close EXIT INT TERM HUP; '
320
+ f'{cmd}'
321
+ )
299
322
 
300
323
 
301
324
  def _fleet_attach_macos(tmux: str) -> tuple[bool, str]:
@@ -360,7 +383,7 @@ def _spawn_fleet_tab(cmd: str) -> tuple[bool, str]:
360
383
  if not tmux:
361
384
  return False, "tmux not installed (brew install tmux -> tabbed fleet window)"
362
385
  label = _fleet_tab_name(cmd)
363
- wrapped = _fleet_wrap(cmd)
386
+ wrapped = _fleet_wrap(cmd, tmux)
364
387
  try:
365
388
  fresh_session = _tmux(tmux, "has-session", "-t", f"={_FLEET_SESSION}").returncode != 0
366
389
  win_id = ""
@@ -684,10 +707,20 @@ def _write_fleet_native_agent(cmd: str) -> Path:
684
707
  "disown",
685
708
  run_line,
686
709
  "MC_RC=$?",
687
- # clean exit (0, or 143 = hostd stop sweep): close ONLY this tab.
688
- # Crash: keep tab + scrollback (debugging > clean window).
689
- 'if { [ "$MC_RC" = "0" ] || [ "$MC_RC" = "143" ]; } && [ -n "$MC_TTY" ]; then',
690
- " /usr/bin/osascript"
710
+ # RAII / opener-owns-teardown (Samuel P0 'vergo de terminales'
711
+ # professional pattern): close OUR OWN native tab on ANY exit clean
712
+ # stop, recycle-exit, OR crash so a dead tab never accumulates (Ian's
713
+ # ~40 stale native tabs). The osascript self-targets by MC_TTY (closes
714
+ # ONLY the tab whose tty is ours) so a LIVE sibling is never touched.
715
+ # Replaces the old keep-crashed-tab-for-debugging behavior. Kill-switch
716
+ # MESHCODE_FLEET_WINDOW_CLOSE=0 restores it; =dry logs intent.
717
+ 'case "${MESHCODE_FLEET_WINDOW_CLOSE:-}" in',
718
+ ' 0|off|false|no) { [ "$MC_RC" != "0" ] && [ "$MC_RC" != "143" ]; } && echo "[meshcode] agent exited rc=$MC_RC — tab kept (close disabled)" ;;',
719
+ ' dry|dryrun|dry-run) echo "[meshcode] DRY-RUN: would close own tab (rc=$MC_RC)" ;;',
720
+ ' *)',
721
+ ' { [ "$MC_RC" != "0" ] && [ "$MC_RC" != "143" ]; } && echo "[meshcode] agent exited rc=$MC_RC — closing tab (1 agent = 1 terminal)"',
722
+ ' if [ -n "$MC_TTY" ]; then',
723
+ " /usr/bin/osascript"
691
724
  " -e 'on run argv'"
692
725
  " -e 'tell application \"Terminal\"'"
693
726
  " -e 'repeat with w in windows'"
@@ -699,9 +732,9 @@ def _write_fleet_native_agent(cmd: str) -> Path:
699
732
  " -e 'end repeat'"
700
733
  " -e 'end tell'"
701
734
  " -e 'end run' \"$MC_TTY\" >/dev/null 2>&1",
702
- "else",
703
- ' echo "[meshcode] agent exited rc=$MC_RC — tab kept for debugging"',
704
- "fi",
735
+ ' fi',
736
+ ' ;;',
737
+ 'esac',
705
738
  ]
706
739
  p["dir"].mkdir(parents=True, exist_ok=True)
707
740
  script_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
@@ -34,7 +34,7 @@ REGISTRY_PATH = WORKSPACES_ROOT / ".registry.json"
34
34
 
35
35
  # Samuel directive 2026-06-17: all agents boot with the best model by default.
36
36
  # Overrides (settings.json "model" or mc_agent_model_pref RPC) still respected.
37
- PLATFORM_DEFAULT_MODEL = "opus"
37
+ PLATFORM_DEFAULT_MODEL = "claude-opus-4-8"
38
38
 
39
39
 
40
40
  def _pretrust_claude_workspace(*paths) -> None:
@@ -769,6 +769,29 @@ def _detect_editor() -> Optional[str]:
769
769
  except Exception:
770
770
  pass
771
771
 
772
+ # macOS/Linux fallback: a freshly `npm i -g` claude (or claude.ai installer)
773
+ # may not be on this process's PATH yet (GUI launch / stale shell) — qa
774
+ # cold-start finding: posix had no fallback, only Windows did. Check common
775
+ # global-bin locations + ask npm for its global prefix.
776
+ if sys.platform != "win32":
777
+ home = os.path.expanduser("~")
778
+ posix_candidates = [
779
+ os.path.join(home, ".local", "bin", "claude"),
780
+ "/opt/homebrew/bin/claude",
781
+ "/usr/local/bin/claude",
782
+ os.path.join(home, ".npm-global", "bin", "claude"),
783
+ ]
784
+ for c in posix_candidates:
785
+ if c and os.path.isfile(c):
786
+ return c
787
+ try:
788
+ prefix = subprocess.check_output(["npm", "prefix", "-g"], text=True, timeout=5).strip()
789
+ p = os.path.join(prefix, "bin", "claude")
790
+ if os.path.isfile(p):
791
+ return p
792
+ except Exception:
793
+ pass
794
+
772
795
  return None
773
796
 
774
797
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.150
3
+ Version: 2.11.153
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.11.150"
7
+ version = "2.11.153"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
File without changes
File without changes
File without changes
File without changes