meshcode 2.11.115__tar.gz → 2.11.116__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 (93) hide show
  1. {meshcode-2.11.115 → meshcode-2.11.116}/PKG-INFO +1 -1
  2. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/__init__.py +1 -1
  3. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/_session_handoff_template.py +49 -0
  4. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/comms_v4.py +1 -1
  5. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/meshcode_mcp/server.py +7 -240
  6. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/run_agent.py +37 -0
  7. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode.egg-info/PKG-INFO +1 -1
  8. {meshcode-2.11.115 → meshcode-2.11.116}/pyproject.toml +1 -1
  9. {meshcode-2.11.115 → meshcode-2.11.116}/README.md +0 -0
  10. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/__main__.py +0 -0
  11. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/_stop_hook_template.py +0 -0
  12. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/ascii_art.py +0 -0
  13. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/atomic_push.py +0 -0
  14. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/claude_update.py +0 -0
  15. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/cli.py +0 -0
  16. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/compat.py +0 -0
  17. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/daemon.py +0 -0
  18. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/date_parse.py +0 -0
  19. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/doctor.py +0 -0
  20. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/error_hints.py +0 -0
  21. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/exceptions.py +0 -0
  22. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/hooks/__init__.py +0 -0
  23. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/hooks/repo_path_lock.py +0 -0
  24. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/hostd.py +0 -0
  25. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/invites.py +0 -0
  26. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/launcher.py +0 -0
  27. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/launcher_install.py +0 -0
  28. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/meshcode_mcp/__init__.py +0 -0
  29. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/meshcode_mcp/__main__.py +0 -0
  30. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/meshcode_mcp/backend.py +0 -0
  31. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/meshcode_mcp/realtime.py +0 -0
  32. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
  33. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/meshcode_mcp/test_backend.py +0 -0
  34. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
  35. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
  36. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
  37. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  38. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  39. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/preferences.py +0 -0
  40. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/protocol_handler.py +0 -0
  41. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/protocol_v2.py +0 -0
  42. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/quickstart.py +0 -0
  43. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/rpc_allowlist.py +0 -0
  44. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/scripts/check_secrets.py +0 -0
  45. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/scripts/race_rate_harness.py +0 -0
  46. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/secrets.py +0 -0
  47. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/self_update.py +0 -0
  48. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/setup_clients.py +0 -0
  49. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/supervisor.py +0 -0
  50. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/up.py +0 -0
  51. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode/upload.py +0 -0
  52. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode.egg-info/SOURCES.txt +0 -0
  53. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode.egg-info/dependency_links.txt +0 -0
  54. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode.egg-info/entry_points.txt +0 -0
  55. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode.egg-info/requires.txt +0 -0
  56. {meshcode-2.11.115 → meshcode-2.11.116}/meshcode.egg-info/top_level.txt +0 -0
  57. {meshcode-2.11.115 → meshcode-2.11.116}/setup.cfg +0 -0
  58. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_auto_update_hardening.py +0 -0
  59. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_autonomous_closegap_1.py +0 -0
  60. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_autonomous_closegap_2.py +0 -0
  61. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_autonomous_closegap_3.py +0 -0
  62. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_autonomous_prompt_inject.py +0 -0
  63. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_boot_bug_regression.py +0 -0
  64. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_color_truecolor.py +0 -0
  65. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_core.py +0 -0
  66. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_cross_agent_messaging.py +0 -0
  67. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_date_parse.py +0 -0
  68. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_doctor.py +0 -0
  69. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_epistemic_v1_python_sdk.py +0 -0
  70. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_epistemic_v1_stop_conditions.py +0 -0
  71. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_esc_deaf_state.py +0 -0
  72. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_exceptions.py +0 -0
  73. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_file_upload.py +0 -0
  74. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_init_device_code.py +0 -0
  75. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_install_guard.py +0 -0
  76. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_lease_sigterm_release.py +0 -0
  77. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_mark_read_batch.py +0 -0
  78. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_marketplace_ratings.py +0 -0
  79. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_migration_integrity.py +0 -0
  80. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_realtime_event_freshness.py +0 -0
  81. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_rls_cross_tenant.py +0 -0
  82. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_rpc_grants.py +0 -0
  83. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_rpc_migrations.py +0 -0
  84. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_run_agent_dry_run.py +0 -0
  85. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_run_agent_no_server_import.py +0 -0
  86. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_security_regressions.py +0 -0
  87. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_self_update_user_site.py +0 -0
  88. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_sentinel.py +0 -0
  89. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_setup_path.py +0 -0
  90. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_sleep_signals.py +0 -0
  91. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_status_enum_coverage.py +0 -0
  92. {meshcode-2.11.115 → meshcode-2.11.116}/tests/test_stay_on_loop_hook.py +0 -0
  93. {meshcode-2.11.115 → meshcode-2.11.116}/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.115
3
+ Version: 2.11.116
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.115"
2
+ __version__ = "2.11.116"
3
3
 
4
4
  # Exception hierarchy — eagerly imported (lightweight, no deps)
5
5
  from meshcode.exceptions import ( # noqa: F401
@@ -161,6 +161,53 @@ def _request_recycle_if_marked(project_dir) -> None:
161
161
  sys.stderr.write(f"[session_handoff_write] recycle-request skipped: {e}\\n")
162
162
 
163
163
 
164
+ def _persist_handoff_to_memory(project_dir, handoff) -> None:
165
+ """L6 M6.1 (task 84c426d4, 2.11.114): mirror the handoff into
166
+ mc_agent_memory key='session_handoff' so the server-side boot
167
+ continuity_capsule (mig 456) can surface it on the NEXT session even
168
+ when the local handoff.json is gone (new host, wiped workspace).
169
+ Same best-effort creds pattern as _request_recycle_if_marked —
170
+ any failure silently skips; handoff.json already covers the local path.
171
+ """
172
+ try:
173
+ mcp = json.loads((project_dir / ".mcp.json").read_text(encoding="utf-8"))
174
+ env = (next(iter((mcp.get("mcpServers") or {}).values()), {}) or {}).get("env", {}) or {}
175
+ url = env.get("SUPABASE_URL"); key = env.get("SUPABASE_KEY")
176
+ agent = env.get("MESHCODE_AGENT"); project = env.get("MESHCODE_PROJECT")
177
+ if not (url and key and agent):
178
+ return
179
+ api_key = os.environ.get("MESHCODE_API_KEY")
180
+ if not api_key:
181
+ try:
182
+ import importlib
183
+ api_key = importlib.import_module("meshcode.secrets").get_api_key(
184
+ profile=env.get("MESHCODE_KEYCHAIN_PROFILE") or "default")
185
+ except Exception:
186
+ api_key = None
187
+ if not api_key:
188
+ return
189
+ turns = handoff.get("turns") or []
190
+ compact = {
191
+ "trigger": handoff.get("trigger"),
192
+ "captured_at_session": handoff.get("session_id"),
193
+ "tail": [{"role": t["role"], "text": t["text"][:400]} for t in turns[-8:]],
194
+ }
195
+ import urllib.request as _u
196
+ body = json.dumps({
197
+ "p_api_key": api_key, "p_agent_name": agent,
198
+ "p_key": "session_handoff", "p_value": compact,
199
+ "p_tier": "episodic", "p_project_name": project,
200
+ }).encode("utf-8")
201
+ req = _u.Request(
202
+ url.rstrip("/") + "/rest/v1/rpc/mc_memory_set",
203
+ data=body, method="POST",
204
+ headers={"apikey": key, "Authorization": "Bearer " + key,
205
+ "Content-Type": "application/json"})
206
+ _u.urlopen(req, timeout=5).read()
207
+ except Exception as e: # noqa: BLE001 — never block compaction
208
+ sys.stderr.write(f"[session_handoff_write] memory-persist skipped: {e}\\n")
209
+
210
+
164
211
  def main() -> int:
165
212
  try:
166
213
  raw = sys.stdin.read()
@@ -185,6 +232,8 @@ def main() -> int:
185
232
  tmp.replace(d / "handoff.json")
186
233
  except OSError as e:
187
234
  sys.stderr.write(f"[session_handoff_write] skipped: {e}\\n")
235
+ # L6 M6.1: mirror to server-side memory for the boot continuity capsule.
236
+ _persist_handoff_to_memory(_project_dir(), handoff)
188
237
  # CTX-CLOSE-RELAUNCH (task 400fc536): now that the thread is snapshotted,
189
238
  # commander-tier sessions ask the server to recycle at the next task-edge.
190
239
  _request_recycle_if_marked(_project_dir())
@@ -1886,7 +1886,7 @@ def _start_heartbeat_daemon(project, name, agent_pid=None):
1886
1886
  " if not check_still_leased(pid):\n"
1887
1887
  " sys.exit(0)\n"
1888
1888
  " post('/rest/v1/rpc/mc_heartbeat', {'p_project_id':pid,'p_agent_name':name})\n"
1889
- " time.sleep(30)\n"
1889
+ " time.sleep(10)\n" # R2-3 (.116): 30s->10s so the fork's agent-alive check (self-exit on recycle/stop) tightens the stale-heartbeat window to <=10s
1890
1890
  )
1891
1891
  # Windows: start_new_session kwarg doesn't exist. Use creationflags.
1892
1892
  _popen_kwargs = {
@@ -1160,6 +1160,11 @@ def _is_consequential(tool_name: str) -> bool:
1160
1160
 
1161
1161
 
1162
1162
  def _check_decision_gate(tool_name: str) -> Optional[Dict[str, Any]]:
1163
+ # R2-4 kill-list (.116): epistemic suite removed (meshcode_decide is gone) —
1164
+ # the gate would otherwise block consequential tools demanding a tool that
1165
+ # no longer exists. Permanently open.
1166
+ return None
1167
+
1163
1168
  """Returns None when the gate is open; returns an error dict to be
1164
1169
  surfaced as the tool result when the gate is closed. Soft-fail on RPC
1165
1170
  error (gate opens) so a transient DB blip doesn't trap the agent."""
@@ -1842,7 +1847,7 @@ Then: set_status(online,ready) → check() → tasks() → auto_wake() → statu
1842
1847
 
1843
1848
  COMMUNICATE BY CALLING TOOLS, not by thinking aloud. Cross-mesh: send(to="agent@meshwork"). Reference docs (memory/scratchpad/account ops) → recall agent_protocol_quick_ref when needed.
1844
1849
 
1845
- DECISIONS: when you face a non-trivial pick (architectural choice, risky migration, scope decision), call meshcode_decide(decision, alternatives, evidence_refs) BEFORE acting — returns a trace_id. Call meshcode_outcome(trace_id, outcome, outcome_eval) when the result becomes observable. Use meshcode_explain(memory_key=…) or (decision_substring=…) to surface prior traces + open contradictions. Memory hints with open_contradictions take a -0.2 ranking penalty — treat as a flag, not a veto.
1850
+ Memory hints with open_contradictions take a -0.2 ranking penalty — treat as a flag, not a veto.
1846
1851
 
1847
1852
  ESCALATION RULES:
1848
1853
  - MUST escalate to human: destructive ops (drop/delete/rm -rf), irreversible changes (force-push main, mig without rollback), product-direction decisions (what feature next), legal/compliance, security (auth/secrets/PII), spend > $50 USD.
@@ -4316,13 +4321,7 @@ async def meshcode_wait(timeout_seconds: int = 20, include_acks: bool = False) -
4316
4321
  # surface autonomy on the refused-path too so check
4317
4322
  # parity holds when wait short-circuits on unread msgs.
4318
4323
  resp["you_are_autonomous"] = _get_autonomous_mode()
4319
- # c0ab14c1 Phase 2: time-awareness on the wait delivery path.
4320
- try:
4321
- _pa = _promise_awareness(_get_api_key())
4322
- if _pa:
4323
- resp["time_awareness"] = _pa
4324
- except Exception:
4325
- pass
4324
+ # time_awareness removed with promise infra (R2-4 kill-list, .116)
4326
4325
  return resp
4327
4326
  # Ack-only batch — fall through to wait loop
4328
4327
  except Exception:
@@ -6141,15 +6140,6 @@ def meshcode_boot() -> Dict[str, Any]:
6141
6140
  except Exception:
6142
6141
  pass
6143
6142
 
6144
- # c0ab14c1 Phase 2: open-promise time-awareness (what I owe / what I'm
6145
- # waiting on, with overdue/elapsed minutes). Soft-fail; only added when
6146
- # there's something open.
6147
- try:
6148
- _pa = _promise_awareness(api_key)
6149
- if _pa:
6150
- resp["time_awareness"] = _pa
6151
- except Exception:
6152
- pass
6153
6143
 
6154
6144
  return resp
6155
6145
 
@@ -6724,115 +6714,6 @@ def _promise_expires_iso(eta_minutes: int) -> Optional[str]:
6724
6714
  return (_dt.now(_tz.utc) + _td(minutes=eta_minutes)).isoformat()
6725
6715
 
6726
6716
 
6727
- def _promise_awareness(api_key: str) -> Optional[Dict[str, Any]]:
6728
- """Compact time-awareness block for boot/wait payloads: what I owe + what I'm
6729
- waiting on, with overdue/elapsed minutes. None when there's nothing open."""
6730
- try:
6731
- r = be.sb_rpc("mc_promises_involving", {
6732
- "p_api_key": api_key,
6733
- "p_agent_name": AGENT_NAME,
6734
- })
6735
- except Exception:
6736
- return None
6737
- if not isinstance(r, dict) or not r.get("ok"):
6738
- return None
6739
- owe = r.get("i_owe") or []
6740
- waiting = r.get("waiting_on") or []
6741
- if not owe and not waiting:
6742
- return None
6743
- return {
6744
- "i_owe": owe,
6745
- "waiting_on": waiting,
6746
- "overdue_i_owe": sum(1 for p in owe if isinstance(p, dict) and p.get("overdue")),
6747
- "overdue_waiting_on": sum(1 for p in waiting if isinstance(p, dict) and p.get("overdue")),
6748
- "hint": "Act on overdue items you owe; for items you're waiting on, the server is already nudging the responsible agent. meshcode_fulfill(promise_id) when done.",
6749
- }
6750
-
6751
-
6752
- @mcp.tool()
6753
- @with_working_status
6754
- def meshcode_promise(to: str, what: str, eta_minutes: int) -> Dict[str, Any]:
6755
- """Register a time-bound commitment YOU are responsible for: "I'll do <what>
6756
- for <to> within <eta_minutes>". If the ETA passes unfulfilled, the server
6757
- proactively pings YOU (exponential backoff, auto-mutes) so nothing stalls
6758
- waiting on you. Call meshcode_fulfill(promise_id) when done.
6759
-
6760
- Use this whenever you tell another agent an ETA. Horizon cap = 24h
6761
- (1440 min) — for longer commitments create a task instead.
6762
-
6763
- Args:
6764
- to: the agent you're committing to (the waiter / beneficiary).
6765
- what: short description of what you'll deliver.
6766
- eta_minutes: minutes from now until you expect to deliver (1..1440).
6767
- """
6768
- if not to or not to.strip():
6769
- return {"ok": False, "error": "to (recipient agent) is required"}
6770
- if not what or not what.strip():
6771
- return {"ok": False, "error": "what (promise text) is required"}
6772
- expires = _promise_expires_iso(eta_minutes)
6773
- if expires is None:
6774
- return {"ok": False, "error": "eta_minutes must be an integer 1..1440 (24h); use a task for longer"}
6775
- return be.sb_rpc("mc_record_promise", {
6776
- "p_api_key": _get_api_key(),
6777
- "p_to_target": to.strip(),
6778
- "p_text": what.strip(),
6779
- "p_expires_at": expires,
6780
- "p_source_msg_id": None,
6781
- "p_agent_name": AGENT_NAME, # responsible = me
6782
- })
6783
-
6784
-
6785
- @mcp.tool()
6786
- @with_working_status
6787
- def meshcode_expect(of: str, what: str, eta_minutes: int) -> Dict[str, Any]:
6788
- """Register that you are WAITING on another agent: "<of> will do <what>
6789
- within <eta_minutes>". If it doesn't happen in time, the server nudges
6790
- <of> (the RESPONSIBLE agent) — not you. This is the anti-stalemate path:
6791
- use it whenever you're blocked on someone else (e.g. "you push first, then
6792
- I deploy"). Cancel with meshcode_fulfill(promise_id) once they deliver.
6793
-
6794
- Args:
6795
- of: the agent you're waiting on (the responsible party).
6796
- what: short description of what you expect them to do.
6797
- eta_minutes: minutes from now you expect it by (1..1440).
6798
- """
6799
- if not of or not of.strip():
6800
- return {"ok": False, "error": "of (agent you're waiting on) is required"}
6801
- if not what or not what.strip():
6802
- return {"ok": False, "error": "what (expectation text) is required"}
6803
- expires = _promise_expires_iso(eta_minutes)
6804
- if expires is None:
6805
- return {"ok": False, "error": "eta_minutes must be an integer 1..1440 (24h); use a task for longer"}
6806
- # responsible = `of`; waiter = me. Server nudges `of` when overdue.
6807
- return be.sb_rpc("mc_record_promise", {
6808
- "p_api_key": _get_api_key(),
6809
- "p_to_target": AGENT_NAME,
6810
- "p_text": what.strip(),
6811
- "p_expires_at": expires,
6812
- "p_source_msg_id": None,
6813
- "p_agent_name": of.strip(), # responsible = the other agent
6814
- })
6815
-
6816
-
6817
- @mcp.tool()
6818
- @with_working_status
6819
- def meshcode_fulfill(promise_id: str) -> Dict[str, Any]:
6820
- """Mark a promise/expectation fulfilled — stops all follow-up nudges
6821
- immediately. Call this the moment the committed work is delivered (or the
6822
- thing you were waiting on arrives).
6823
-
6824
- Args:
6825
- promise_id: the id returned by meshcode_promise / meshcode_expect (or
6826
- surfaced in a promise_overdue nudge or the boot/wait time_awareness block).
6827
- """
6828
- if not promise_id or not str(promise_id).strip():
6829
- return {"ok": False, "error": "promise_id is required"}
6830
- return be.sb_rpc("mc_fulfill_promise", {
6831
- "p_api_key": _get_api_key(),
6832
- "p_promise_id": str(promise_id).strip(),
6833
- })
6834
-
6835
-
6836
6717
  # ----------------- PENDING APPROVALS (flagship: GO/no-go gate) -----------------
6837
6718
  # Task bb396c9b. The agent asks for a GO/no-go before a gated action; a human (or an
6838
6719
  # autonomous commander) approves/rejects with one button in the dashboard panel.
@@ -7363,120 +7244,6 @@ def meshcode_recall_search(query: str) -> Dict[str, Any]:
7363
7244
  # mig 305-308 SQL substrate; mig 313 memory_hints contradictions + ranking;
7364
7245
  # mig 314 public.* wrappers. Task b4befbfd.
7365
7246
 
7366
- @mcp.tool()
7367
- @with_working_status
7368
- def meshcode_decide(
7369
- decision: str,
7370
- alternatives: Optional[List[str]] = None,
7371
- evidence_refs: Optional[List[Dict[str, Any]]] = None,
7372
- tier: str = "episodic",
7373
- ) -> Dict[str, Any]:
7374
- """Log a non-trivial decision to mc_traces (architectural choice, risky migration,
7375
- scope decision). Returns {trace_id}. Pair with meshcode_outcome later when the
7376
- result becomes observable so the team can learn from outcomes.
7377
-
7378
- Args:
7379
- decision: What you decided (one sentence).
7380
- alternatives: Other options you considered (list of strings).
7381
- evidence_refs: Pointers to evidence (list of {type, id|key} dicts).
7382
- type values: 'memory','msg','task','file','rpc'.
7383
- tier: 'episodic' (default) | 'reference' | 'critical'.
7384
- """
7385
- if not decision or not decision.strip():
7386
- return {"ok": False, "error": "decision required"}
7387
- # MESH-IMPROVE-9 soft-gate: warn if a prior trace is missing its outcome
7388
- # >24h. v1 = warning only (continues). v2 followup tightens to refuse.
7389
- api_key = _get_api_key()
7390
- warning = None
7391
- try:
7392
- _gate = be.sb_rpc("mc_check_unwritten_outcome", {
7393
- "p_api_key": api_key,
7394
- "p_project_id": _PROJECT_ID,
7395
- "p_agent_name": AGENT_NAME,
7396
- "p_age_hours": 24,
7397
- })
7398
- if isinstance(_gate, dict) and _gate.get("ok") and _gate.get("has_unwritten"):
7399
- warning = {
7400
- "type": "unwritten_outcome",
7401
- "open_trace_id": _gate.get("oldest_trace_id"),
7402
- "open_trace_decision": _gate.get("decision"),
7403
- "open_trace_age_seconds": _gate.get("age_seconds"),
7404
- "hint": _gate.get("hint"),
7405
- "note": "soft-gate v1: this WARNS but does not block. v2 will refuse. Close prior trace with meshcode_outcome().",
7406
- }
7407
- except Exception:
7408
- pass # Soft-fail when mig 340 not yet deployed
7409
- result = be.sb_rpc("mc_decide", {
7410
- "p_api_key": api_key,
7411
- "p_project_id": _PROJECT_ID,
7412
- "p_agent_name": AGENT_NAME,
7413
- "p_decision": decision.strip(),
7414
- "p_alternatives": json.dumps(alternatives or []),
7415
- "p_evidence_refs": json.dumps(evidence_refs or []),
7416
- "p_tier": tier,
7417
- })
7418
- if warning and isinstance(result, dict):
7419
- result["outcome_gate_warning"] = warning
7420
- return result
7421
-
7422
-
7423
- @mcp.tool()
7424
- @with_working_status
7425
- def meshcode_outcome(
7426
- trace_id: str,
7427
- outcome: str,
7428
- outcome_eval: str,
7429
- ) -> Dict[str, Any]:
7430
- """Record the observable outcome of a prior decision (logged via meshcode_decide).
7431
- Call this when results become visible — even partial results help future-you and
7432
- the team learn what worked.
7433
-
7434
- Args:
7435
- trace_id: UUID returned by meshcode_decide.
7436
- outcome: Free-form description of what happened.
7437
- outcome_eval: 'success' | 'failure' | 'partial' | 'unknown'.
7438
- """
7439
- if outcome_eval not in ("success", "failure", "partial", "unknown"):
7440
- return {"ok": False, "error": "outcome_eval must be success|failure|partial|unknown"}
7441
- return be.sb_rpc("mc_outcome", {
7442
- "p_api_key": _get_api_key(),
7443
- "p_project_id": _PROJECT_ID,
7444
- "p_agent_name": AGENT_NAME,
7445
- "p_trace_id": trace_id,
7446
- "p_outcome": outcome,
7447
- "p_outcome_eval": outcome_eval,
7448
- })
7449
-
7450
-
7451
- @mcp.tool()
7452
- @with_working_status
7453
- def meshcode_explain(
7454
- memory_key: Optional[str] = None,
7455
- decision_substring: Optional[str] = None,
7456
- trace_limit: int = 20,
7457
- ) -> Dict[str, Any]:
7458
- """Surface recent traces + open contradictions tied to a memory key or decision
7459
- substring. Use when you want to understand why a memory ranks the way it does,
7460
- or recall the chain of reasoning around a past choice. Exactly one of
7461
- (memory_key, decision_substring) is required.
7462
-
7463
- Args:
7464
- memory_key: Match traces whose evidence_refs reference this key.
7465
- decision_substring: ILIKE match against the decision text.
7466
- trace_limit: Cap on returned traces (1..200, default 20).
7467
- """
7468
- if not memory_key and not decision_substring:
7469
- return {"ok": False, "error": "one of (memory_key, decision_substring) required"}
7470
- return be.sb_rpc("mc_explain", {
7471
- "p_api_key": _get_api_key(),
7472
- "p_project_id": _PROJECT_ID,
7473
- "p_agent_name": AGENT_NAME,
7474
- "p_memory_key": memory_key,
7475
- "p_decision_substring": decision_substring,
7476
- "p_trace_limit": trace_limit,
7477
- })
7478
-
7479
-
7480
7247
  # ----------------- BUG REPORTING -----------------
7481
7248
 
7482
7249
  @mcp.tool()
@@ -1112,6 +1112,43 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
1112
1112
  else:
1113
1113
  cmd = [effective_editor]
1114
1114
 
1115
+ # R2-7 Phase 1 (mig 467): per-agent model preference. settings.json "model"
1116
+ # wins (skip RPC); else mc_agent_model_pref -> append --model. Full soft-fail
1117
+ # (no pref / no creds / network error -> current behavior). 4s RPC timeout.
1118
+ try:
1119
+ _settings_model = None
1120
+ _sp = ws / ".claude" / "settings.json"
1121
+ if _sp.exists():
1122
+ _settings_model = (json.loads(_sp.read_text(encoding="utf-8")) or {}).get("model")
1123
+ if not _settings_model:
1124
+ _mcp_cfg = json.loads(mcp_json_path.read_text(encoding="utf-8"))
1125
+ _env = (next(iter((_mcp_cfg.get("mcpServers") or {}).values()), {}) or {}).get("env", {}) or {}
1126
+ _url = _env.get("SUPABASE_URL"); _anon = _env.get("SUPABASE_KEY")
1127
+ _proj = _env.get("MESHCODE_PROJECT"); _agent_n = _env.get("MESHCODE_AGENT")
1128
+ _ak = os.environ.get("MESHCODE_API_KEY")
1129
+ if not _ak:
1130
+ try:
1131
+ from meshcode import secrets as _sec
1132
+ _ak = _sec.get_api_key(profile=_env.get("MESHCODE_KEYCHAIN_PROFILE") or "default")
1133
+ except Exception:
1134
+ _ak = None
1135
+ if _url and _anon and _proj and _agent_n and _ak:
1136
+ import urllib.request as _u
1137
+ _req = _u.Request(
1138
+ _url.rstrip("/") + "/rest/v1/rpc/mc_agent_model_pref",
1139
+ data=json.dumps({"p_api_key": _ak, "p_project_name": _proj,
1140
+ "p_agent_name": _agent_n}).encode(),
1141
+ method="POST",
1142
+ headers={"apikey": _anon, "Authorization": "Bearer " + _anon,
1143
+ "Content-Type": "application/json"})
1144
+ with _u.urlopen(_req, timeout=4) as _resp:
1145
+ _pref = (json.loads(_resp.read().decode()) or {}).get("model_pref")
1146
+ if _pref:
1147
+ cmd += ["--model", _pref]
1148
+ print(f"[meshcode] Model pref (dashboard): {_pref}", file=sys.stderr)
1149
+ except Exception as _me:
1150
+ print(f"[meshcode] model-pref skipped: {_me}", file=sys.stderr)
1151
+
1115
1152
  if mode == "bypass":
1116
1153
  bypass_check_cmd = effective_editor if not pinned_version else "claude"
1117
1154
  if pinned_version or _claude_supports_bypass(bypass_check_cmd):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.115
3
+ Version: 2.11.116
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.115"
7
+ version = "2.11.116"
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