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.
- {meshcode-2.11.150 → meshcode-2.11.153}/PKG-INFO +1 -1
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/__init__.py +1 -1
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/comms_v4.py +41 -3
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/doctor.py +101 -1
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/protocol_handler.py +60 -27
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/run_agent.py +24 -1
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.150 → meshcode-2.11.153}/pyproject.toml +1 -1
- {meshcode-2.11.150 → meshcode-2.11.153}/README.md +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/__main__.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/_session_handoff_template.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/cli.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/compat.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/daemon.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/helper_visuals.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/hooks/__init__.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/hooks/repo_path_lock.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/hostd.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/invites.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/launcher.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/swarm.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/meshcode_mcp/test_swarm.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/preferences.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/rpc_allowlist.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/secrets.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/self_update.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/up.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode/upload.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/setup.cfg +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_autonomous_prompt_inject.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_core.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_doctor.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_helper_visuals.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_hostd_launch_pinned_env.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_hostd_serve_discovery_split.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_hostd_zombie_sessions.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_live_mesh_guard.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_pretrust_claude.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_session_replay_gate.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_stop_ghost_terminal.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_swarm_events.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_task_progress.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_terminal_lifecycle.py +0 -0
- {meshcode-2.11.150 → meshcode-2.11.153}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -319,8 +319,28 @@ def get_project_id(project_name):
|
|
|
319
319
|
if rows:
|
|
320
320
|
return rows[0]["id"]
|
|
321
321
|
|
|
322
|
-
#
|
|
323
|
-
#
|
|
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
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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 dies — clean
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
#
|
|
688
|
-
#
|
|
689
|
-
|
|
690
|
-
|
|
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
|
-
|
|
703
|
-
'
|
|
704
|
-
|
|
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
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|