code-context-control 2.154.0__py3-none-any.whl → 2.155.0__py3-none-any.whl

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.
cli/c3.py CHANGED
@@ -91,7 +91,7 @@ console = Console() if HAS_RICH else None
91
91
  # Config
92
92
  CONFIG_DIR = ".c3"
93
93
  CONFIG_FILE = ".c3/config.json"
94
- __version__ = "2.154.0"
94
+ __version__ = "2.155.0"
95
95
 
96
96
  # The PreToolUse matcher for native subagent calls (installer and hub migration).
97
97
  AGENT_MATCHER = "Agent|Task"
cli/hook_session_open.py CHANGED
@@ -30,6 +30,12 @@ the hub, and it skips a project that already has a live UI port, so a
30
30
  ``C3_NO_UI_AUTOSTART=1`` or
31
31
  ``.c3/config.json → {"ui": {"autostart_on_session": false}}``.
32
32
 
33
+ A ``clear`` (or an in-session ``resume``) keeps the MCP server it already
34
+ has, under a host session id that just ended. This hook finds that server
35
+ by the host pid both of them share and hands it the new id
36
+ (``services.host_handoff``, v2.155.0); the server then writes the
37
+ ``C3 connected <new id>`` row the desktop sentinel is waiting for.
38
+
33
39
  ``source: "compact"`` writes NOTHING: a compaction is the same session and
34
40
  the same MCP process, and a "Session started" toast on every compaction
35
41
  would teach the user to ignore the real ones.
@@ -48,6 +54,8 @@ from __future__ import annotations
48
54
  import json
49
55
  import os
50
56
  import sys
57
+ import time
58
+ import uuid
51
59
  from pathlib import Path
52
60
 
53
61
  sys.path.insert(0, str(Path(__file__).parent.parent))
@@ -56,6 +64,11 @@ from cli._hook_utils import detect_host, find_project, log_hook_error # noqa: E
56
64
  from cli.hook_session_end import short_id # noqa: E402
57
65
 
58
66
  _SILENT_SOURCES = {"compact"}
67
+ # How long one session's UI launch holds the project against the next. A
68
+ # server lands in the registry only after it binds its port, a few seconds
69
+ # after Popen; this covers that gap with room to spare. After it, the live
70
+ # registry entry (``_ui_running``) is the guard again.
71
+ _LAUNCH_GRACE_S = 90.0
59
72
 
60
73
 
61
74
  def _ui_autostart_enabled(project: Path) -> bool:
@@ -96,7 +109,10 @@ def _ui_running(pm, project: Path) -> bool:
96
109
  """True when a live UI server is already registered for this project.
97
110
 
98
111
  This is what keeps `resume` / `clear` — both real SessionStart events —
99
- from stacking a second server on every reconnect.
112
+ from stacking a second server on every reconnect. A registered server
113
+ whose port is quiet but whose pid is running is still "running": it is
114
+ starting up, or busy, and a second one would only be the leak this
115
+ check exists to prevent (2.155.0).
100
116
  """
101
117
  target = os.path.normcase(str(project))
102
118
  for entry in pm._read_registry():
@@ -107,8 +123,72 @@ def _ui_running(pm, project: Path) -> bool:
107
123
  same = os.path.normcase(str(Path(raw).resolve())) == target
108
124
  except Exception:
109
125
  same = os.path.normcase(raw) == target
110
- if same and entry.get("port") and pm._port_alive(entry["port"]):
126
+ if not same or not entry.get("port"):
127
+ continue
128
+ if pm._port_alive(entry["port"]):
129
+ return True
130
+ if entry.get("pid") and pm._pid_alive(entry.get("pid")):
131
+ return True
132
+ return False
133
+
134
+
135
+ def _launch_lock(project: Path) -> Path:
136
+ return project / ".c3" / "ui_launch.lock"
137
+
138
+
139
+ def _claim_launch(project: Path) -> bool:
140
+ """Atomically claim the right to launch this project's UI server.
141
+
142
+ ``_ui_running`` only sees a server once it has registered its port, a
143
+ few seconds after launch. Sessions that open inside that window (an
144
+ agent spawning several at once, or a resume racing a startup) each saw
145
+ no live UI and each launched one, which is how a project ended up with
146
+ two servers. The first claimant creates ``.c3/ui_launch.lock`` with
147
+ O_EXCL; anyone else inside ``_LAUNCH_GRACE_S`` stands down.
148
+
149
+ A lock older than the grace window is stale (the launch died, or the
150
+ server since exited). It is taken over by renaming it to a unique name,
151
+ which only one contender can do, then the create is retried. If the
152
+ lock cannot be handled at all the answer is True, which is the
153
+ behaviour from before this lock existed: a missing UI is worse than a
154
+ rare duplicate.
155
+ """
156
+ lock = _launch_lock(project)
157
+ try:
158
+ lock.parent.mkdir(parents=True, exist_ok=True)
159
+ except Exception:
160
+ return True
161
+ for _ in range(3):
162
+ try:
163
+ fd = os.open(str(lock), os.O_CREAT | os.O_EXCL | os.O_WRONLY)
164
+ except FileExistsError:
165
+ try:
166
+ age = time.time() - lock.stat().st_mtime
167
+ except FileNotFoundError:
168
+ continue # released between our create and stat; retry
169
+ except Exception:
170
+ return True
171
+ if age < _LAUNCH_GRACE_S:
172
+ return False
173
+ stale = lock.with_name(f"ui_launch.stale-{uuid.uuid4().hex}")
174
+ try:
175
+ os.rename(str(lock), str(stale))
176
+ except FileNotFoundError:
177
+ continue # another contender took it over first
178
+ except Exception:
179
+ return False
180
+ try:
181
+ stale.unlink()
182
+ except Exception:
183
+ pass
184
+ continue
185
+ except Exception:
111
186
  return True
187
+ try:
188
+ os.write(fd, f"{os.getpid()} {time.time():.0f}\n".encode("ascii"))
189
+ finally:
190
+ os.close(fd)
191
+ return True
112
192
  return False
113
193
 
114
194
 
@@ -136,6 +216,8 @@ def ensure_ui(project: Path, host_session_id: str = "") -> str:
136
216
  return "unregistered"
137
217
  if _ui_running(pm, project):
138
218
  return "already-running"
219
+ if not _claim_launch(project):
220
+ return "launch-pending"
139
221
  # launch_session copies os.environ into the detached child, which is
140
222
  # where cli/server._register_session reads the ownership from.
141
223
  os.environ["C3_UI_OWNER"] = "session"
@@ -150,6 +232,22 @@ def ensure_ui(project: Path, host_session_id: str = "") -> str:
150
232
  return "error"
151
233
 
152
234
 
235
+ def hand_off_runtime(project: Path, host: str, host_sid: str, start_source: str) -> list:
236
+ """Point the MCP server this host already runs at its new session id.
237
+
238
+ Returns the C3 session ids handed off (usually one, often none: a
239
+ ``startup`` has no server yet). Never raises.
240
+ """
241
+ if not host_sid:
242
+ return []
243
+ try:
244
+ from services.host_handoff import hand_off
245
+ return hand_off(project, host, host_sid, start_source)
246
+ except Exception as exc:
247
+ log_hook_error("hook_session_open", exc)
248
+ return []
249
+
250
+
153
251
  def run(payload: dict, project_path: Path | None = None):
154
252
  """Core logic — importable by the dispatcher and tests. Returns None."""
155
253
  start_source = str(payload.get("source") or "").strip().lower()
@@ -178,6 +276,7 @@ def run(payload: dict, project_path: Path | None = None):
178
276
  kind="session", ref_id=host_sid)
179
277
  except Exception:
180
278
  pass
279
+ hand_off_runtime(project, host, host_sid, start_source)
181
280
  ensure_ui(project, host_sid)
182
281
  return None
183
282
 
cli/mcp_server.py CHANGED
@@ -165,6 +165,93 @@ def _link_host_session(svc, host_sid: str) -> None:
165
165
  pass
166
166
 
167
167
 
168
+ # ── Host handoff (2.155.0): follow a /clear to its new host session id ─────
169
+ # The pid of the IDE / agent CLI above this process (services.host_handoff).
170
+ # Written into every heartbeat; the SessionStart hook matches on it.
171
+ _HOST_PID = 0
172
+ # How often the heartbeat thread looks for a handoff file. The desktop
173
+ # sentinel gives a new session 30 s to produce a `C3 connected` row.
174
+ _HANDOFF_POLL_S = 5
175
+
176
+
177
+ def _rebind_host_session(svc, host_sid: str, start_source: str = "") -> bool:
178
+ """Adopt ``host_sid`` as this server's host session id. Returns True when
179
+ something changed.
180
+
181
+ Everything that was derived from the old id is redone: the session
182
+ record (what ``_grants.session_id`` and the access guard read), the
183
+ host env var (what a c3_project proxy runtime built later would read),
184
+ the host↔C3 link file, the heartbeat — and the ``C3 connected <id>``
185
+ notification, which is what C3 Desk's "did not connect" sentinel waits
186
+ for after the hook's ``Session started <id>``.
187
+ """
188
+ host_sid = str(host_sid or "").strip()
189
+ session = getattr(getattr(svc, "session_mgr", None), "current_session", None)
190
+ if not host_sid or not isinstance(session, dict):
191
+ return False
192
+ previous = str(session.get("host_session_id") or "")
193
+ if previous == host_sid:
194
+ return False
195
+ session["host_session_id"] = host_sid
196
+ ide = getattr(svc, "ide_name", "") or _IDE_NAME
197
+ try:
198
+ from core.host import session_env_var
199
+ var = session_env_var(ide)
200
+ if var:
201
+ os.environ[var] = host_sid
202
+ except Exception:
203
+ pass
204
+ try:
205
+ import dataclasses
206
+ base = getattr(svc, "host_context", None) or _HOST
207
+ ctx = dataclasses.replace(base, host_session_id=host_sid)
208
+ svc.host_context = ctx
209
+ if getattr(svc, "session_mgr", None) is not None:
210
+ svc.session_mgr.host_context = ctx
211
+ except Exception:
212
+ pass
213
+ with _host_sid_lock:
214
+ _host_sid_cache["value"] = host_sid
215
+ _host_sid_cache["read_at"] = time.monotonic()
216
+ _host_sid_cache["linked"] = ""
217
+ _link_host_session(svc, host_sid)
218
+ try:
219
+ svc.activity_log.log("session_rebind", {
220
+ "session_id": str(session.get("id") or ""),
221
+ "host_session_id": host_sid,
222
+ "previous_host_session_id": previous,
223
+ "start_source": str(start_source or ""),
224
+ })
225
+ except Exception:
226
+ pass
227
+ try:
228
+ from services import session_live
229
+ session_live.beat(svc.project_path, str(session.get("id") or ""),
230
+ host_session_id=host_sid, ide=ide, host_pid=_HOST_PID)
231
+ except Exception:
232
+ pass
233
+ _notify_mcp_ready(svc)
234
+ return True
235
+
236
+
237
+ def _check_handoff(svc) -> bool:
238
+ """Consume a pending handoff file for this session, if any. Never raises."""
239
+ try:
240
+ session = getattr(getattr(svc, "session_mgr", None), "current_session", None) or {}
241
+ c3_id = str(session.get("id") or "")
242
+ if not c3_id:
243
+ return False
244
+ from services import host_handoff
245
+ data = host_handoff.read_handoff(svc.project_path, c3_id)
246
+ if not data:
247
+ return False
248
+ host_handoff.clear_handoff(svc.project_path, c3_id)
249
+ return _rebind_host_session(svc, str(data.get("host_session_id") or ""),
250
+ str(data.get("start_source") or ""))
251
+ except Exception:
252
+ return False
253
+
254
+
168
255
  def _notify_mcp_ready(svc) -> None:
169
256
  """``kind="mcp"`` / ``C3 connected`` — the runtime is ready to serve (D0b).
170
257
 
@@ -341,19 +428,36 @@ async def lifespan(server):
341
428
  if _live_session_id:
342
429
  from services import session_live
343
430
 
431
+ global _HOST_PID
432
+ try:
433
+ from services.host_handoff import host_process_id
434
+ _HOST_PID = int(host_process_id() or 0)
435
+ except Exception:
436
+ _HOST_PID = 0
437
+
344
438
  def _do_beat():
345
439
  session_live.beat(services.project_path, _live_session_id,
346
440
  host_session_id=_host_session_id(services) or "",
347
- ide=_IDE_NAME)
441
+ ide=_IDE_NAME, host_pid=_HOST_PID)
348
442
 
349
443
  _do_beat()
350
444
 
351
445
  def _bg_heartbeat():
352
- while not _heartbeat_stop.wait(timeout=session_live.HEARTBEAT_INTERVAL_S):
446
+ # Wakes every _HANDOFF_POLL_S to look for a handoff (a /clear
447
+ # moved this server to a new host session id), and beats every
448
+ # HEARTBEAT_INTERVAL_S as before.
449
+ last_beat = time.monotonic()
450
+ while not _heartbeat_stop.wait(timeout=_HANDOFF_POLL_S):
353
451
  try:
354
- _do_beat()
452
+ _check_handoff(services)
355
453
  except Exception:
356
454
  pass
455
+ if time.monotonic() - last_beat >= session_live.HEARTBEAT_INTERVAL_S:
456
+ last_beat = time.monotonic()
457
+ try:
458
+ _do_beat()
459
+ except Exception:
460
+ pass
357
461
 
358
462
  threading.Thread(target=_bg_heartbeat, daemon=True,
359
463
  name="c3-session-heartbeat").start()
@@ -423,6 +527,9 @@ async def lifespan(server):
423
527
  # notification within 30 s, so this must be the LAST thing before the
424
528
  # handshake proceeds and must never be skipped on a partial init.
425
529
  _notify_mcp_ready(services)
530
+ # A /clear that landed while this process was still starting left its
531
+ # handoff waiting; take it now rather than at the first poll.
532
+ _check_handoff(services)
426
533
 
427
534
  try:
428
535
  yield services
@@ -517,6 +624,10 @@ def _ensure_repo_map_once(rt) -> None:
517
624
  def _svc(ctx: Context) -> C3Runtime:
518
625
  rt = ctx.request_context.lifespan_context
519
626
  _ensure_repo_map_once(rt)
627
+ # A tool call right after a /clear must already run under the new host
628
+ # id (grants and leases are keyed by it): one stat per call, before the
629
+ # access guard binds the session.
630
+ _check_handoff(rt)
520
631
  access_guard.bind_session(_grants.session_id(rt))
521
632
  return rt
522
633
 
cli/server.py CHANGED
@@ -63,19 +63,8 @@ _HUB_CONFIG_FILE = _GLOBAL_C3_DIR / "hub_config.json"
63
63
 
64
64
 
65
65
  def _registry_read() -> list:
66
- try:
67
- if _REGISTRY_FILE.exists():
68
- with open(_REGISTRY_FILE, encoding="utf-8") as f:
69
- return json.load(f)
70
- except Exception:
71
- pass
72
- return []
73
-
74
-
75
- def _registry_write(entries: list):
76
- _GLOBAL_C3_DIR.mkdir(parents=True, exist_ok=True)
77
- with open(_REGISTRY_FILE, "w", encoding="utf-8") as f:
78
- json.dump(entries, f, indent=2)
66
+ from services import ui_registry
67
+ return ui_registry.read(_REGISTRY_FILE)
79
68
 
80
69
 
81
70
  def _read_hub_config() -> dict:
@@ -103,32 +92,54 @@ def _port_alive(port: int) -> bool:
103
92
  return False
104
93
 
105
94
 
106
- def _register_session(port: int, project_path: str, project_name: str):
95
+ # Seconds between a running server's re-assertions of its own registry entry
96
+ # (services.ui_registry.ensure). A lost entry — an older hub rewriting the
97
+ # file from a stale snapshot, a crash mid-write — comes back within one tick
98
+ # instead of leaving a server nothing can find, reap or reuse.
99
+ _REGISTRY_REASSERT_S = 60
100
+
101
+
102
+ def _registry_entry(port: int, project_path: str, project_name: str) -> dict:
103
+ # owner says who asked for this server: "session" when an IDE session
104
+ # start launched it (cli/hook_session_open), "user" for `c3 ui`, the
105
+ # hub's Open UI button and the TUI. Only session-owned servers are
106
+ # ever reaped automatically (ProjectManager.sweep_registry).
107
+ return {
108
+ "port": port,
109
+ "project_path": project_path,
110
+ "project_name": project_name,
111
+ "started_at": time.time(),
112
+ "pid": os.getpid(),
113
+ "owner": (os.environ.get("C3_UI_OWNER") or "user").strip().lower(),
114
+ "owner_session": (os.environ.get("C3_UI_OWNER_SESSION") or "").strip(),
115
+ }
116
+
117
+
118
+ def _register_session(port: int, project_path: str, project_name: str) -> dict:
119
+ """Upsert this server's entry under the registry lock (2.155.0).
120
+
121
+ This used to drop every OTHER entry whose port missed a 0.3 s connect —
122
+ a sibling server busy re-indexing was deregistered by the next one to
123
+ start, and lived on as an orphan. Pruning is the hub sweep's job now,
124
+ and it only drops an entry whose pid is gone as well.
125
+ """
126
+ from services import ui_registry
127
+ entry = _registry_entry(port, project_path, project_name)
107
128
  with _REGISTRY_LOCK:
108
- entries = _registry_read()
109
- # Remove our own port and any stale (dead) entries
110
- entries = [e for e in entries if e.get("port") != port and _port_alive(e.get("port", 0))]
111
- # owner says who asked for this server: "session" when an IDE session
112
- # start launched it (cli/hook_session_open), "user" for `c3 ui`, the
113
- # hub's Open UI button and the TUI. Only session-owned servers are
114
- # ever reaped automatically (ProjectManager.sweep_registry).
115
- entries.append({
116
- "port": port,
117
- "project_path": project_path,
118
- "project_name": project_name,
119
- "started_at": time.time(),
120
- "pid": os.getpid(),
121
- "owner": (os.environ.get("C3_UI_OWNER") or "user").strip().lower(),
122
- "owner_session": (os.environ.get("C3_UI_OWNER_SESSION") or "").strip(),
123
- })
124
- _registry_write(entries)
129
+ ui_registry.upsert(entry, _REGISTRY_FILE)
130
+ return entry
131
+
132
+
133
+ def _reassert_registration(entry: dict) -> bool:
134
+ from services import ui_registry
135
+ with _REGISTRY_LOCK:
136
+ return ui_registry.ensure(entry, _REGISTRY_FILE)
125
137
 
126
138
 
127
139
  def _unregister_session(port: int):
140
+ from services import ui_registry
128
141
  with _REGISTRY_LOCK:
129
- entries = _registry_read()
130
- entries = [e for e in entries if e.get("port") != port]
131
- _registry_write(entries)
142
+ ui_registry.remove_ports([port], _REGISTRY_FILE)
132
143
 
133
144
 
134
145
  def init_services(project_path: str):
@@ -4536,8 +4547,19 @@ def run_server(
4536
4547
  # Fallback chain: custom meta name → folder name → parent/folder for disambiguation
4537
4548
  _folder = _resolved.name or _resolved.parent.name or "project"
4538
4549
  _proj_name = (_meta.get("name") or "").strip() or _folder
4539
- _register_session(port, str(_resolved), _proj_name)
4550
+ _entry = _register_session(port, str(_resolved), _proj_name)
4540
4551
  atexit.register(_unregister_session, port)
4552
+
4553
+ def _bg_reassert():
4554
+ while True:
4555
+ time.sleep(_REGISTRY_REASSERT_S)
4556
+ try:
4557
+ _reassert_registration(_entry)
4558
+ except Exception:
4559
+ pass
4560
+
4561
+ threading.Thread(target=_bg_reassert, daemon=True,
4562
+ name="c3-ui-registry-reassert").start()
4541
4563
  except Exception:
4542
4564
  pass
4543
4565
 
cli/tools/_grants.py CHANGED
@@ -51,6 +51,24 @@ def session_id(svc) -> str:
51
51
  return str(session.get("id", "") or "") or f"pid-{os.getpid()}"
52
52
 
53
53
 
54
+ def agent_id(svc) -> str:
55
+ """One caller label for explicit locks and implicit edit leases.
56
+
57
+ Runtime host identity belongs to the caller, including c3_project proxies;
58
+ the target project's preferred editor must not override it.
59
+ """
60
+ from core.host import resolve_host
61
+ from services.agent_locks import agent_id_for
62
+
63
+ provider = getattr(svc, "ide_name", "")
64
+ if not isinstance(provider, str) or not provider.strip():
65
+ session = getattr(getattr(svc, "session_mgr", None), "current_session", None) or {}
66
+ provider = session.get("source_ide", "")
67
+ if not isinstance(provider, str) or not provider.strip():
68
+ provider = resolve_host(str(getattr(svc, "project_path", "") or "")).provider
69
+ return agent_id_for(session_id(svc), provider=provider)
70
+
71
+
54
72
  def allow(svc, denial, *, tool: str, op: str, path,
55
73
  peek: bool = False) -> str | None:
56
74
  """The `[c3-override:granted]` line when a live grant permits this exact
cli/tools/edit.py CHANGED
@@ -566,7 +566,8 @@ def _write_gate(svc, path: Path, rel: str, file_path: str, op: str,
566
566
  "holder": holder.get("agent_id", ""),
567
567
  "held_for_s": held_for},
568
568
  agent_locks.refusal(holder, rel), "lock held")
569
- agent_locks.lease(str(path), svc.project_path, session_id, intent=intent)
569
+ agent_locks.lease(str(path), svc.project_path, session_id, intent=intent,
570
+ agent_id=_grants.agent_id(svc))
570
571
  return None
571
572
 
572
573
 
cli/tools/locks.py CHANGED
@@ -40,7 +40,7 @@ def handle_locks(action: str, paths: str, intent: str, ttl_s: int,
40
40
  """Route c3_locks actions."""
41
41
  project = str(svc.project_path)
42
42
  session = _session_id(svc)
43
- agent = al.agent_id_for(session)
43
+ agent = _grants.agent_id(svc)
44
44
  args = {"action": action}
45
45
 
46
46
  cfg = al.config(project)
cli/tools/project.py CHANGED
@@ -54,6 +54,19 @@ def _runtime_for(path: str):
54
54
  return shared_cache().get(path)
55
55
 
56
56
 
57
+ class _CallerLabelRuntime:
58
+ """Use the caller's provider label without changing target session ownership."""
59
+
60
+ def __init__(self, target, caller):
61
+ from cli.tools import _grants
62
+
63
+ self._target = target
64
+ self.ide_name = _grants.agent_id(caller).split(":", 1)[0]
65
+
66
+ def __getattr__(self, name):
67
+ return getattr(self._target, name)
68
+
69
+
57
70
  def _is_registered(path: str) -> bool:
58
71
  """True when *path* is in the cross-project registry. Fails closed."""
59
72
  try:
@@ -524,6 +537,11 @@ def handle_project(action, svc, finalize, *, project="", query="", file_path="",
524
537
  return done(
525
538
  f"[c3_project:error] Could not load '{resolved['name']}': {e}", "error")
526
539
 
540
+ # Label edit leases with the requesting host, without mutating the cached
541
+ # runtime or borrowing the caller's session/grant identity in the target.
542
+ if action == "edit":
543
+ fsvc = _CallerLabelRuntime(fsvc, svc)
544
+
527
545
  banner = f"[c3_project:{resolved['name']}] {action}\n"
528
546
  try:
529
547
  body = _proxy(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.154.0
3
+ Version: 2.155.0
4
4
  Summary: Local MCP code-intelligence for AI coding tools: surgical search/read/edit, agent-config version history, path-level access + masking guards, and a multi-project hub.
5
5
  Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -1,7 +1,7 @@
1
1
  cli/__init__.py,sha256=ec66drCZGNMRU4V6ov0zVhYZph1us12Vn8OvG_LJyRY,22
2
2
  cli/_hook_utils.py,sha256=RhfS2WS5VMSi2w2Jxcts1wg5WC-UFp89JOQvcO5Jjb8,23002
3
3
  cli/_shell_writes.py,sha256=XzUNlUDljmg-wZxK03xHnRe36WeQxe4tpO9HKVFwP5o,5090
4
- cli/c3.py,sha256=FWPIwjhUOyJUG75lTOIVl2jxNj5bKmk7ffziGfLZSg8,434309
4
+ cli/c3.py,sha256=FYmFF0w1cmoNAHhaxEvvnM6-7uuU0jDV70KIc9eAPDM,434309
5
5
  cli/docs.html,sha256=pPxcP5ieY0DcfEDvegwIcRxbz1woLZoKLVSF-4JVy0Q,146784
6
6
  cli/edits.html,sha256=UjAhoCmBmQ89cklGvJqzC6eyNP2tc8H6T-e01DVkLvE,43418
7
7
  cli/hook_access_guard.py,sha256=tzK3pQBGehDDZi6fECcYk8nRhy0QkmUtTF2TuJG7nbY,27928
@@ -20,15 +20,15 @@ cli/hook_pretool_enforce.py,sha256=pSFYgqiXktJPpNuzGXiFPhcGQRDN9QjMjpQHliO_7XQ,3
20
20
  cli/hook_prompt_recall.py,sha256=LHyubu36pf5h3hbLBe0yH-yGhAlwLJdsc7hfa7WB1h4,3471
21
21
  cli/hook_read.py,sha256=ZWZldgqGSNz78COhlgETQ7yxdOW5vWIIHUet9gZKhxk,8176
22
22
  cli/hook_session_end.py,sha256=toFEPDKRoutDuVOmuOgHmQ8CM0o7tF7yALhMxJz7VEM,3482
23
- cli/hook_session_open.py,sha256=Badeikv37D7b6inSHbVjAxtfdH4xiJKFT1O52fgreGs,7443
23
+ cli/hook_session_open.py,sha256=VXStM1vsA2kwLZxczAvetJGf4aDT1dPlWYMeEW4ow4Y,11387
24
24
  cli/hook_session_stats.py,sha256=I-2NeLlV1NxeqKtTM2JH9haSpFU7r4qG0u8pfCJ80YU,8725
25
25
  cli/hook_terse_advisor.py,sha256=W_zaTfX2akgX-ifuUTsLu2H1atM8XmWt6jpPoeU7Pm0,6252
26
26
  cli/hub_server.py,sha256=VgDAlVmWgvh57KUbMgL2PT4303fmlr578QLk6ehe2pA,227863
27
27
  cli/hub_ui.html,sha256=v41R_JAH4Jwodpy3tbeBUel-80FbNmffBrey-QH5Q64,2963
28
28
  cli/mcp_proxy.py,sha256=r-Qhlie7gCtotex8yQUudCyBLRUK-IW6cNIW5RhBW0o,17648
29
- cli/mcp_server.py,sha256=VzzsJFckLdtwXYnX_CH-57eObOUP-IN-i5IIb8BWu8Y,74956
29
+ cli/mcp_server.py,sha256=YR3mPwLcgvJiai8dSHxh4ZK8hGtFt1D-SOk7Khm-vTA,79502
30
30
  cli/progress.py,sha256=qTkNy0howOD3hXraMo4ok3dYTpBW5yG_tenKfF4UDM0,1470
31
- cli/server.py,sha256=dJOOXYU1zmN-UBuMmHhv8XlnknSyO8xj7mXWSDAGIBY,173188
31
+ cli/server.py,sha256=SQFONagf4Mmsv2Z0fdnr8cxAFUsBE3nPQ5XUZLgtjI8,174039
32
32
  cli/ui.html,sha256=uuPwIJNKDQH_fsaR3xNB2AKky9WzKfaXnKr_k3aTtxo,5715
33
33
  cli/ui_legacy.html,sha256=cI8tC6RKmE2NIJOcsu7CY-zT4VznjcbD6NTjxb_fvUY,378460
34
34
  cli/ui_nano.html,sha256=UAwQ6bbTOXAoGq191AZ7slhngR9edJSa3IhqpynveDg,27740
@@ -81,7 +81,7 @@ cli/hub_ui/components/toasts.js,sha256=caj_x3npcM_wMv9E_xEde88jyfXI-jt8RrDQW_yXz
81
81
  cli/hub_ui/components/topbar.js,sha256=xg8RqDmJpweISw55NFT07tMiwrFaG9fpDEHHblzkLk4,4817
82
82
  cli/tools/__init__.py,sha256=HO6eVKzDm1KPqsKTHY6lfIsnFnv3C7Bd9o3Q_V88idg,127
83
83
  cli/tools/_edit_report.py,sha256=uZuhMQABN6z9R_FXusoYNNrdYrES3rhbADXmJupwc0c,8392
84
- cli/tools/_grants.py,sha256=xJwEZWC9r4exq0OuabQy-fFMZFayUYZe0Eub7NS3ikQ,4974
84
+ cli/tools/_grants.py,sha256=pWJ2TgjsAAk0lelMBmHS8EiX5mpBnEUfgQ2-TiMIzow,5768
85
85
  cli/tools/_helpers.py,sha256=xhF6sSc9wcJjLQmFFipnUgxQusU1PDPcl0DGMGcgK6E,9420
86
86
  cli/tools/agent.py,sha256=RmEEh5HhCZLvYu0gSRcwA_NENpd4iIYJW3exBzT8kVs,47983
87
87
  cli/tools/artifacts.py,sha256=SjazShDFIzOpAZW7uMv9itBktXnc6pqQKZeaen8GgzI,9379
@@ -90,17 +90,17 @@ cli/tools/ci.py,sha256=1--Ak18ikHkO7q4HWlDCVac-U969eEFejkd-24T0xuY,20057
90
90
  cli/tools/compress.py,sha256=PcgxEw7KfofmJsTLfRf3mjWMxyNlqkcSNzMTlT27SW0,12534
91
91
  cli/tools/credentials.py,sha256=NczO0KT3LTJlD3ZedXWRAtH_8YTPhya0Txja3ystdzE,20039
92
92
  cli/tools/delegate.py,sha256=TrHPXEyufQh0in3Ay7XcOG5wQwCYbnbz9r4naIuhbN8,126990
93
- cli/tools/edit.py,sha256=YPcrXfOvaUCiddg9qABFXqEk9kb9TNHMLFEo2Xq-sBQ,44715
93
+ cli/tools/edit.py,sha256=9AOUtEG6sLEsbtKpd6cQyq66vCXNaOeoEKit3ikVJyc,44769
94
94
  cli/tools/edit_verify.py,sha256=3Y156LikO3Q4v0OSNjKDJUaOB3nRfceFvXCmpSV8amA,11858
95
95
  cli/tools/edits.py,sha256=Qkcl7Asa_qyIH2aIn5IYamfKavtfEZF429Bmg8TF0aY,10963
96
96
  cli/tools/federate.py,sha256=tE65p06gM6O_yzg08SKeficFCZUH0yOFhCHFUjlTRo0,5126
97
97
  cli/tools/filter.py,sha256=mwNabPe7bCvdsIGFsWc4E1HsfiRYx5ZevnxdsitJTAw,12702
98
98
  cli/tools/impact.py,sha256=J6dvMrma1E2jGlhi3dIeQXhdwya5xljrJ92eiRuDaIE,7198
99
99
  cli/tools/jira.py,sha256=BkaebUWAxwwNn2eYx1B9CXBH2wvJRhFW3ViwggFnzIY,30202
100
- cli/tools/locks.py,sha256=FvuNxToU-5ZE2m_C6fTrn1jKERoxR4cbtxNb_b8mXqA,5882
100
+ cli/tools/locks.py,sha256=1cuFuLSmB8OrPAFIf23rveOp3dkX2nmVMppW2YkCRKA,5879
101
101
  cli/tools/memory.py,sha256=yDDRsEngeFcjb6nXUrhRZXuboasXfixJeyltKhDbZD4,24935
102
102
  cli/tools/override.py,sha256=LiZFgoDN-35NXVwldnYrcCzfBl8MhQQ3tKVVkOG5d_o,9217
103
- cli/tools/project.py,sha256=MWzi7tBTQ03p_6PavmG9ZLXZHO0IhjKEJNyB0oZWTFc,24248
103
+ cli/tools/project.py,sha256=CWnrE-tJuNm8koiGNmzUNFAfrIUlhr7Joz6W64YVea4,24843
104
104
  cli/tools/read.py,sha256=9W-FhALienlmWeLQnsUA9tvbTgh9IGJzbSYyoT9Wd7I,21439
105
105
  cli/tools/search.py,sha256=BZh3E7YggjCWOC1lJz0YNcRsdDLWsJiEmONtgUW2rEo,32397
106
106
  cli/tools/session.py,sha256=IYNgcNz_saQDvilX6iB-SClLpfsGmz_grgMY5PFztPI,9371
@@ -132,13 +132,13 @@ cli/ui/components/sessions.js,sha256=FIKtil76B8tCkAmcFV7hlj6GQ_DCJK2jCzvEmdK7NBE
132
132
  cli/ui/components/settings.js,sha256=FXcn-UQE2mXC6HFsLeEas-wAUFKt0OJaLySroZQMhGA,84095
133
133
  cli/ui/components/sidebar.js,sha256=K2ym2kUgpbyG-EBA_wBIIiqQY8qTatsiBZwzseMWuIQ,9939
134
134
  cli/ui/components/tasks.js,sha256=vyKQ3uwoppMwvdEaHlhWXW4oWcAisx4NveqzMhsYqHo,38438
135
- code_context_control-2.154.0.dist-info/licenses/LICENSE,sha256=l8Kh5QCNWNvR6kIt8L0BUZvc2LAFiHv2c-FnsGnUZf4,11301
135
+ code_context_control-2.155.0.dist-info/licenses/LICENSE,sha256=l8Kh5QCNWNvR6kIt8L0BUZvc2LAFiHv2c-FnsGnUZf4,11301
136
136
  core/__init__.py,sha256=TSDCEcM4V7gcZVM3w2ykJaqEUch4Dkon-rivV17T73s,2501
137
137
  core/codex_patch.py,sha256=CxvT_rQIwPNTRJZj9EHJmx0ZdOeSLxjiixuLI3Z7lPc,2107
138
138
  core/config.py,sha256=wCjrZKXRywUbR6WRcO7optyOirAQ2DeRNdPCU0X5zIY,23762
139
139
  core/grok_payload.py,sha256=EEwhvkKnCT6Rk9tc-DWDA__toE44244Xr4d6p7SoJwE,5365
140
140
  core/hook_command.py,sha256=SvTDUaBLg1Qb4ZdAgpe14YA_8BSnkGJsd3ByPtqBsU0,1032
141
- core/host.py,sha256=yBzjJjSs2ATyCpGD_IHOJ8n2Pn6RFPhnKHq819CFzC4,1585
141
+ core/host.py,sha256=WUzS-572l9azYvu1Y4fmAVvb_0g4xyXqzsayJP_zH3M,2075
142
142
  core/ide.py,sha256=0sk2MzrgIScE5fN1cIpjMrNqE5j1Hz_kyPfCT2w-ac8,7061
143
143
  core/mcp_toml.py,sha256=VNZCjJsr4II_jX3cmjYzDh_OQ7Nk34mLnkg528Tl1GM,7047
144
144
  core/web_security.py,sha256=HwgK5aBATfvRIeO_4gWgzK77gKwGuzVlmiuNGm4gGxI,7884
@@ -194,7 +194,7 @@ services/access_telemetry.py,sha256=9pOCLS9LgDtM7Z4cCEyl7osxhj-m3wsPubcXolBKuV4,
194
194
  services/activity_log.py,sha256=c_jTOsR-d1d5B3u67eg8mRA7LZhtP6H1-6leeGa0Go4,7990
195
195
  services/agent_base.py,sha256=a-gdSd_jtZtbjXo1WS8CnWCagXgKaGZd5ShcG6s0kT4,4809
196
196
  services/agent_downshift.py,sha256=hGFg-knqa6NUqmZUijyTDV82Fz88ieh1YjN_mG5tz8U,8444
197
- services/agent_locks.py,sha256=1eCOiJ4UkpandhlgHqtO5DpwOjN7bGWYrEEE1puoFc0,23524
197
+ services/agent_locks.py,sha256=iaS3Wz6iCS3iTkMi1zoGXlSnze9dphEOH_1am4V_4M8,23670
198
198
  services/agents.py,sha256=q3GgxyzomyjhbixARDQrSM3EpjIhboeKQY5jsQeHQ0g,83609
199
199
  services/agents_workflow.py,sha256=ttve887E6SIY2twHx_OxwFBEtLUcuok72_8g3ZkOAkE,502
200
200
  services/artifact_defs.py,sha256=4bJAcg2maJCtMhY3ub_IxZg49bVg1mD_B5Z4Bn5NF0o,9805
@@ -244,6 +244,7 @@ services/file_memory.py,sha256=7E-2I1iBIJOTp-PEjoimLZZpLVLygoUF3fNKWjyF6FQ,40556
244
244
  services/git_context.py,sha256=FFdA8Y46op4DYsUnZsXLTqxIjgxb0q_d-hkFTbosC8Q,9518
245
245
  services/grok_integration.py,sha256=aiWVmG9e-0-9_MjrJ_p0dsbTSdAYFkLfsUMI7onmz0A,8299
246
246
  services/grok_transcript.py,sha256=E4im_OIUrU-jDfw3sum_gCuG6FiONgZdlYsIhS-zdlM,2884
247
+ services/host_handoff.py,sha256=zCPbPwSFGXuNUlGlHAWlmy7V7TizhduwS0upD3EYMsw,12103
247
248
  services/host_sessions.py,sha256=5R0DSU8z-CI6kKek4yLCwe1FShxZTOLe6dlYnH5-hoI,3722
248
249
  services/hub_service.py,sha256=z2MZ2Rg9NLNXxQ17e05XGF_f6qSTDWLYtoC3WpuQfGU,2634
249
250
  services/index_store.py,sha256=SWHHKQB_wYww1ubtKgkLo5FFEqaWhPbw0OAN8x3gk9Q,16453
@@ -277,7 +278,7 @@ services/override_policy.py,sha256=TehVPxfQ8LZWIOUzCnnioIdMUPZZ7JZa1ja77smpW2c,3
277
278
  services/override_requests.py,sha256=H9lSUbfOjkjE1muztaQ58InqqsqhtnKIT-pF2VzvWkg,31148
278
279
  services/override_wake.py,sha256=HjSOXQTDi0irgeMyrTqngL7xLcSOOIgYsVW9yxod4ps,10363
279
280
  services/parser.py,sha256=S_YoKLS3r1xCUVHApXb3BTEfoRAzFhuo1_8XsG0qxVE,63505
280
- services/project_manager.py,sha256=7NvQau5x0PCVagQsXpuXZ4VtXCkCfy9es_v1oEHmzgs,48718
281
+ services/project_manager.py,sha256=dfA8jUjaEBQfX6JMVBy3iwXygb2gtPaI0sv_6QxLRFY,48983
281
282
  services/project_runtime.py,sha256=yyftX48BFjq4B2KcR4KGszDAAa4xR6gn6j0SgTyro1s,11603
282
283
  services/protocol.py,sha256=CWZ0BWWd-jPui-11hZlvGgkNk18gMFhhLWbp-BP35Kk,12861
283
284
  services/proxy_state.py,sha256=u5rd0k6CrOsywZA8FpRu_hMLwhR0TAJhZjy5MdWbCGc,6107
@@ -293,7 +294,7 @@ services/runtime.py,sha256=931eux-zdsXh0M6i6RmXEGAKiXLfa9aDra9V12Q5Z_Y,13767
293
294
  services/scanner.py,sha256=lH0aiDCdCRruKS-VTAD_LsF4zrhMLpMRhFlOZZlw4VM,9613
294
295
  services/session_benchmark.py,sha256=KUcWWC5X088p_ao6DgglUMwj7jCzdecPKxJ7XiGKob0,106255
295
296
  services/session_catalog.py,sha256=H0TrxFQERg26B1Jgx_rwXHTO4lhXhAshj2oIvWHYqZg,46661
296
- services/session_live.py,sha256=JixvsUdmcKGsuZGgWj-iRFJZbQDfQTWoVeIy1-M90A0,5146
297
+ services/session_live.py,sha256=RPFhxqVh0QmKIDn_PN0OxjpK2wHOOrvJkIgsvogMuwQ,5646
297
298
  services/session_manager.py,sha256=IBE71TORRlNuRBhswobD5KzXhfFAijy6KrnmaP1lnuc,57391
298
299
  services/session_preloader.py,sha256=DsTAXMKVtrX9yu1sEFojYDi9-jkSAj1Ylt9JTy57Dow,9883
299
300
  services/shell_jobs.py,sha256=YaC-IOJr_OE4AB95IewtSVx1bZ3wmPIOiNyXiaxjp1g,52986
@@ -306,6 +307,7 @@ services/text_index.py,sha256=r3o4CobTG9jAO9PWazgbWYLY9oi_FgEJ3xwEXrF4KM0,2783
306
307
  services/time_tracker.py,sha256=sZNz8QAYpWhCBwfmvopVyxpMb8H-CrBT25C0_VOT1LY,11056
307
308
  services/tool_classifier.py,sha256=Fgvq0ZcpnCskwtO8a3YI1MiecPNnw6UbPyJQIUwgfiQ,6512
308
309
  services/transcript_index.py,sha256=VQhvgkSyLVEYamXi_YIbiIhBnd0mqFHZlWG2HQu_1EA,12144
310
+ services/ui_registry.py,sha256=K_wzK0x_-jvJFdl28HCBpKow3td_BKhg3vPuei8vU7E,9121
309
311
  services/validation_cache.py,sha256=skFYR7CkSGvFb2gq6dfxOnvjQzz5Boa3jJWT7UfaIhY,5587
310
312
  services/vector_store.py,sha256=satc4MJanC1Wsu_iveMCUXG6K2fd0Ke87JSyy2BMikw,13041
311
313
  services/watcher.py,sha256=WF6aTIUFzg5B5KepuLyUwmx8WvbmhT9IeenzdK0df3U,10779
@@ -337,8 +339,8 @@ tui/screens/search_view.py,sha256=MMHjVdlk3HZSuDBSvq8IGrqv_Mh5Us6YqXQ80bcWSMk,19
337
339
  tui/screens/session_view.py,sha256=eZ1eDwHTvPOck1wCCviixtOaCxIkBT_95ytNNNriGNA,5991
338
340
  tui/screens/stats.py,sha256=p81PjzdaIv7hllb8f45-rlVe4lJZwSdIMqu7e86_u5s,6223
339
341
  tui/screens/ui_view.py,sha256=1QJCgLh2YfgWIpvzRG1KOGXYEaOYX6ojN61Azjf2oX0,2125
340
- code_context_control-2.154.0.dist-info/METADATA,sha256=B9QZpxCE_K6EV78qi2ogB4IVaiW_aCL9BLTKYvxtB0o,28902
341
- code_context_control-2.154.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
342
- code_context_control-2.154.0.dist-info/entry_points.txt,sha256=7kX_WUsDCF2hbXzvbNyscyaBb9AeA-DJY5v_5hN0DlU,93
343
- code_context_control-2.154.0.dist-info/top_level.txt,sha256=wRt41zBybVF3qAiNXHz9BURbkKvUvfhmWWtKMhaw6eE,29
344
- code_context_control-2.154.0.dist-info/RECORD,,
342
+ code_context_control-2.155.0.dist-info/METADATA,sha256=5Nly_ZkXO2Uxav43Yk3sXrfECPN5Wtnk5r8hui7Tt6c,28902
343
+ code_context_control-2.155.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
344
+ code_context_control-2.155.0.dist-info/entry_points.txt,sha256=7kX_WUsDCF2hbXzvbNyscyaBb9AeA-DJY5v_5hN0DlU,93
345
+ code_context_control-2.155.0.dist-info/top_level.txt,sha256=wRt41zBybVF3qAiNXHz9BURbkKvUvfhmWWtKMhaw6eE,29
346
+ code_context_control-2.155.0.dist-info/RECORD,,