cloudagent-daemon 0.2.4__tar.gz → 0.2.5__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 (33) hide show
  1. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/PKG-INFO +1 -1
  2. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/pyproject.toml +1 -1
  3. cloudagent_daemon-0.2.5/src/cloudagent_daemon/__init__.py +1 -0
  4. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/service.py +60 -19
  5. cloudagent_daemon-0.2.4/src/cloudagent_daemon/__init__.py +0 -1
  6. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/.gitignore +0 -0
  7. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/LICENSE +0 -0
  8. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/README.md +0 -0
  9. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/__main__.py +0 -0
  10. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/claude_runner.py +0 -0
  11. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/cli.py +0 -0
  12. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/config.py +0 -0
  13. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/doctor.py +0 -0
  14. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/frame_dispatch.py +0 -0
  15. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/http_outbound.py +0 -0
  16. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/jsonl_logging.py +0 -0
  17. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/login.py +0 -0
  18. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/main_loop.py +0 -0
  19. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/mcp_bridge.py +0 -0
  20. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/metrics.py +0 -0
  21. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/metrics_server.py +0 -0
  22. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/outbound_queue.py +0 -0
  23. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/paths.py +0 -0
  24. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/protocol.py +0 -0
  25. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/runtime_detect.py +0 -0
  26. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/spawn_outbox.py +0 -0
  27. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/spawn_queue.py +0 -0
  28. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/sse_client.py +0 -0
  29. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/transport.py +0 -0
  30. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/unix_socket.py +0 -0
  31. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/workspace.py +0 -0
  32. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/src/cloudagent_daemon/ws_client.py +0 -0
  33. {cloudagent_daemon-0.2.4 → cloudagent_daemon-0.2.5}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudagent-daemon
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: Local daemon for cloudagent — bridges Claude Code subprocesses to the cloudagent backend over WebSocket.
5
5
  Project-URL: Homepage, https://cloudagent.chat
6
6
  Project-URL: Repository, https://codeup.aliyun.com/69bd0a4d29ad98af4065ce71/cloudagent
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cloudagent-daemon"
7
- version = "0.2.4"
7
+ version = "0.2.5"
8
8
  description = "Local daemon for cloudagent — bridges Claude Code subprocesses to the cloudagent backend over WebSocket."
9
9
  authors = [{name = "chaoyin", email = "tudoucyx@gmail.com"}]
10
10
  readme = "README.md"
@@ -0,0 +1 @@
1
+ __version__ = "0.2.5"
@@ -17,6 +17,7 @@ import os
17
17
  import platform
18
18
  import shutil
19
19
  import subprocess
20
+ import time
20
21
  from pathlib import Path
21
22
 
22
23
  from .paths import (
@@ -200,29 +201,69 @@ def install_macos(
200
201
  plist_path.chmod(0o600)
201
202
 
202
203
  uid = os.getuid()
203
- # Reinstall-safe (2026-05-19). A plain ``bootstrap`` is a no-op when
204
- # the service is already loaded — launchctl keeps the OLD in-memory
205
- # plist and never re-reads the file we just wrote. Without a prior
206
- # ``bootout`` a re-run of ``install-service`` silently keeps the
207
- # stale definition (e.g. a changed CLOUDAGENT_DAEMON_PERMISSION_MODE
208
- # never takes effect). So: bootout first (unload any loaded
209
- # instance), then bootstrap genuinely loads the fresh plist.
210
- # ``bootout`` exits non-zero when nothing is loaded (first install)
211
- # — ``check=False`` swallows that, making install == reinstall.
212
- subprocess.run(
213
- ["launchctl", "bootout", f"gui/{uid}/{label}"],
214
- check=False,
215
- )
216
- subprocess.run(
217
- ["launchctl", "bootstrap", f"gui/{uid}", str(plist_path)],
218
- check=False,
219
- )
204
+ domain = f"gui/{uid}"
205
+ service_target = f"{domain}/{label}"
206
+ _launchd_reinstall(domain, service_target, plist_path)
207
+ return plist_path
208
+
209
+
210
+ def _launchd_reinstall(
211
+ domain: str, service_target: str, plist_path: Path
212
+ ) -> None:
213
+ """Reinstall a launchd job: bootout → wait → bootstrap → kickstart.
214
+
215
+ Reinstall-safe + race-safe. Two problems this handles:
216
+
217
+ 1. A plain ``bootstrap`` is a no-op when the service is already
218
+ loaded — launchd keeps the OLD in-memory plist and never re-reads
219
+ the file. So we ``bootout`` first to unload any loaded instance.
220
+
221
+ 2. ``bootout`` is *asynchronous*: launchctl returns before launchd
222
+ has finished tearing the job down. A ``bootstrap`` fired
223
+ immediately races the still-unloading job and fails with
224
+ ``Input/output error`` (5). So after bootout we POLL
225
+ ``launchctl print`` until the job is genuinely gone, then
226
+ bootstrap — with a few retries for slow teardown.
227
+
228
+ Raises RuntimeError if bootstrap never succeeds (cli surfaces it as
229
+ a clean error instead of the old silent "✓ Installed" lie).
230
+ """
231
+ subprocess.run(["launchctl", "bootout", service_target], check=False)
232
+
233
+ # Poll until the job is no longer registered (``print`` exits
234
+ # non-zero). ~5s budget — teardown is normally well under 1s.
235
+ for _ in range(50):
236
+ probe = subprocess.run(
237
+ ["launchctl", "print", service_target],
238
+ capture_output=True,
239
+ check=False,
240
+ )
241
+ if probe.returncode != 0:
242
+ break
243
+ time.sleep(0.1)
244
+
245
+ last_err = ""
246
+ for _ in range(5):
247
+ result = subprocess.run(
248
+ ["launchctl", "bootstrap", domain, str(plist_path)],
249
+ capture_output=True,
250
+ text=True,
251
+ check=False,
252
+ )
253
+ if result.returncode == 0:
254
+ break
255
+ last_err = (result.stderr or result.stdout or "").strip()
256
+ time.sleep(0.3)
257
+ else:
258
+ raise RuntimeError(
259
+ f"launchctl bootstrap failed after retries: {last_err or 'unknown'}"
260
+ )
261
+
220
262
  # Kickstart -k forces an immediate (re)start.
221
263
  subprocess.run(
222
- ["launchctl", "kickstart", "-k", f"gui/{uid}/{label}"],
264
+ ["launchctl", "kickstart", "-k", service_target],
223
265
  check=False,
224
266
  )
225
- return plist_path
226
267
 
227
268
 
228
269
  def uninstall_macos(profile: str | None = None) -> bool:
@@ -1 +0,0 @@
1
- __version__ = "0.2.4"