cloudagent-daemon 0.2.1__tar.gz → 0.2.2__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.1 → cloudagent_daemon-0.2.2}/PKG-INFO +21 -32
  2. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/README.md +20 -31
  3. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/pyproject.toml +1 -1
  4. cloudagent_daemon-0.2.2/src/cloudagent_daemon/__init__.py +1 -0
  5. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/claude_runner.py +36 -1
  6. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/cli.py +39 -0
  7. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/config.py +39 -1
  8. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/main_loop.py +5 -1
  9. cloudagent_daemon-0.2.1/src/cloudagent_daemon/__init__.py +0 -1
  10. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/.gitignore +0 -0
  11. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/LICENSE +0 -0
  12. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/__main__.py +0 -0
  13. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/doctor.py +0 -0
  14. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/frame_dispatch.py +0 -0
  15. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/http_outbound.py +0 -0
  16. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/jsonl_logging.py +0 -0
  17. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/login.py +0 -0
  18. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/mcp_bridge.py +0 -0
  19. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/metrics.py +0 -0
  20. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/metrics_server.py +0 -0
  21. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/outbound_queue.py +0 -0
  22. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/paths.py +0 -0
  23. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/protocol.py +0 -0
  24. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/runtime_detect.py +0 -0
  25. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/service.py +0 -0
  26. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/spawn_outbox.py +0 -0
  27. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/spawn_queue.py +0 -0
  28. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/sse_client.py +0 -0
  29. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/transport.py +0 -0
  30. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/unix_socket.py +0 -0
  31. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/workspace.py +0 -0
  32. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/src/cloudagent_daemon/ws_client.py +0 -0
  33. {cloudagent_daemon-0.2.1 → cloudagent_daemon-0.2.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudagent-daemon
3
- Version: 0.2.1
3
+ Version: 0.2.2
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
@@ -43,56 +43,45 @@ backend, and exits when its work is done.
43
43
 
44
44
  ## Quick start (no clone)
45
45
 
46
- The fastest way to run cloudagent-daemon, slock-style:
47
-
48
- Each command below is a SINGLE LINE — copy the whole line as-is.
49
- Multi-line snippets with `\` continuations are fragile across copy-paste
50
- (some terminals / web `<pre>` blocks drop the trailing `\` and break the
51
- command into separate statements).
52
-
53
- > **Tag-pinned releases.** The `@daemon-vX.Y.Z` ref below points at a frozen
54
- > daemon release (annotated git tag). To upgrade later, re-run the same
55
- > command with the latest tag — see `git ls-remote --tags origin "daemon-v*"`
56
- > on the cloudagent repo, or [the releases list][releases]. Don't replace
57
- > the tag with a branch name (e.g. `cy` / `m5`); branches move and can drag
58
- > in unrelated work-in-progress.
59
-
60
- [releases]: https://codeup.aliyun.com/69bd0a4d29ad98af4065ce71/cloudagent/-/tags?search=daemon-v
46
+ Published to PyPI as [`cloudagent-daemon`](https://pypi.org/project/cloudagent-daemon/).
47
+ Requires Python 3.12+ and Claude Code CLI on PATH.
61
48
 
62
49
  ```bash
63
- # One-shot run (no install) — single line
64
- uvx --from "git+ssh://git@codeup.aliyun.com:/69bd0a4d29ad98af4065ce71/cloudagent.git@daemon-v0.2.1#subdirectory=daemon_cli" cloudagent-daemon start --server-url https://api-fr.cloudagent.chat --api-key sk_machine_***
50
+ # One-shot run (no persistent install)
51
+ uvx cloudagent-daemon start --server-url https://api.cloudagent.chat --api-key sk_machine_***
65
52
  ```
66
53
 
67
54
  ```bash
68
- # Or install persistently — single line, install + start chained with &&
69
- uv tool install --from "git+ssh://git@codeup.aliyun.com:/69bd0a4d29ad98af4065ce71/cloudagent.git@daemon-v0.2.1#subdirectory=daemon_cli" cloudagent-daemon && cloudagent-daemon start --server-url https://api-fr.cloudagent.chat --api-key sk_machine_***
55
+ # Persistent install — install + start chained with && (single line)
56
+ uv tool install cloudagent-daemon && cloudagent-daemon start --server-url https://api.cloudagent.chat --api-key sk_machine_***
70
57
  ```
71
58
 
72
- To upgrade an existing install to the newest tag (forces re-resolve of the
73
- git ref so a moved tag wouldn't silently stick on the old commit anyway —
74
- but tags are immutable here, so the bump in the URL is the real signal):
75
-
76
59
  ```bash
77
- uv tool install --reinstall --force --from "git+ssh://git@codeup.aliyun.com:/69bd0a4d29ad98af4065ce71/cloudagent.git@daemon-v0.2.1#subdirectory=daemon_cli" cloudagent-daemon
60
+ # Or via pipx
61
+ pipx install cloudagent-daemon
78
62
  ```
79
63
 
80
- Once published to PyPI, this becomes:
64
+ To upgrade later:
81
65
 
82
66
  ```bash
83
- uvx cloudagent-daemon start --server-url https://api-fr.cloudagent.chat --api-key sk_machine_***
67
+ uv tool upgrade cloudagent-daemon
84
68
  # or
85
- pipx install cloudagent-daemon
69
+ pipx upgrade cloudagent-daemon
86
70
  ```
87
71
 
88
- ## Install
72
+ ### Developer install (from source)
73
+
74
+ Contributors working off the cloudagent repo can install the unreleased dev
75
+ tip directly from the `cy` branch — requires SSH access to the codeup mirror:
76
+
77
+ ```bash
78
+ uv tool install --from "git+ssh://git@codeup.aliyun.com:/69bd0a4d29ad98af4065ce71/cloudagent.git@cy#subdirectory=daemon_cli" cloudagent-daemon
79
+ ```
89
80
 
90
- Requires Python 3.12+ and Claude Code CLI installed.
81
+ Or editable install for local hacking:
91
82
 
92
83
  ```bash
93
84
  pip install -e ./daemon_cli
94
- # or after release:
95
- # pipx install cloudagent-daemon
96
85
  ```
97
86
 
98
87
  ## Quick start
@@ -11,56 +11,45 @@ backend, and exits when its work is done.
11
11
 
12
12
  ## Quick start (no clone)
13
13
 
14
- The fastest way to run cloudagent-daemon, slock-style:
15
-
16
- Each command below is a SINGLE LINE — copy the whole line as-is.
17
- Multi-line snippets with `\` continuations are fragile across copy-paste
18
- (some terminals / web `<pre>` blocks drop the trailing `\` and break the
19
- command into separate statements).
20
-
21
- > **Tag-pinned releases.** The `@daemon-vX.Y.Z` ref below points at a frozen
22
- > daemon release (annotated git tag). To upgrade later, re-run the same
23
- > command with the latest tag — see `git ls-remote --tags origin "daemon-v*"`
24
- > on the cloudagent repo, or [the releases list][releases]. Don't replace
25
- > the tag with a branch name (e.g. `cy` / `m5`); branches move and can drag
26
- > in unrelated work-in-progress.
27
-
28
- [releases]: https://codeup.aliyun.com/69bd0a4d29ad98af4065ce71/cloudagent/-/tags?search=daemon-v
14
+ Published to PyPI as [`cloudagent-daemon`](https://pypi.org/project/cloudagent-daemon/).
15
+ Requires Python 3.12+ and Claude Code CLI on PATH.
29
16
 
30
17
  ```bash
31
- # One-shot run (no install) — single line
32
- uvx --from "git+ssh://git@codeup.aliyun.com:/69bd0a4d29ad98af4065ce71/cloudagent.git@daemon-v0.2.1#subdirectory=daemon_cli" cloudagent-daemon start --server-url https://api-fr.cloudagent.chat --api-key sk_machine_***
18
+ # One-shot run (no persistent install)
19
+ uvx cloudagent-daemon start --server-url https://api.cloudagent.chat --api-key sk_machine_***
33
20
  ```
34
21
 
35
22
  ```bash
36
- # Or install persistently — single line, install + start chained with &&
37
- uv tool install --from "git+ssh://git@codeup.aliyun.com:/69bd0a4d29ad98af4065ce71/cloudagent.git@daemon-v0.2.1#subdirectory=daemon_cli" cloudagent-daemon && cloudagent-daemon start --server-url https://api-fr.cloudagent.chat --api-key sk_machine_***
23
+ # Persistent install — install + start chained with && (single line)
24
+ uv tool install cloudagent-daemon && cloudagent-daemon start --server-url https://api.cloudagent.chat --api-key sk_machine_***
38
25
  ```
39
26
 
40
- To upgrade an existing install to the newest tag (forces re-resolve of the
41
- git ref so a moved tag wouldn't silently stick on the old commit anyway —
42
- but tags are immutable here, so the bump in the URL is the real signal):
43
-
44
27
  ```bash
45
- uv tool install --reinstall --force --from "git+ssh://git@codeup.aliyun.com:/69bd0a4d29ad98af4065ce71/cloudagent.git@daemon-v0.2.1#subdirectory=daemon_cli" cloudagent-daemon
28
+ # Or via pipx
29
+ pipx install cloudagent-daemon
46
30
  ```
47
31
 
48
- Once published to PyPI, this becomes:
32
+ To upgrade later:
49
33
 
50
34
  ```bash
51
- uvx cloudagent-daemon start --server-url https://api-fr.cloudagent.chat --api-key sk_machine_***
35
+ uv tool upgrade cloudagent-daemon
52
36
  # or
53
- pipx install cloudagent-daemon
37
+ pipx upgrade cloudagent-daemon
54
38
  ```
55
39
 
56
- ## Install
40
+ ### Developer install (from source)
41
+
42
+ Contributors working off the cloudagent repo can install the unreleased dev
43
+ tip directly from the `cy` branch — requires SSH access to the codeup mirror:
44
+
45
+ ```bash
46
+ uv tool install --from "git+ssh://git@codeup.aliyun.com:/69bd0a4d29ad98af4065ce71/cloudagent.git@cy#subdirectory=daemon_cli" cloudagent-daemon
47
+ ```
57
48
 
58
- Requires Python 3.12+ and Claude Code CLI installed.
49
+ Or editable install for local hacking:
59
50
 
60
51
  ```bash
61
52
  pip install -e ./daemon_cli
62
- # or after release:
63
- # pipx install cloudagent-daemon
64
53
  ```
65
54
 
66
55
  ## Quick start
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cloudagent-daemon"
7
- version = "0.2.1"
7
+ version = "0.2.2"
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.2"
@@ -198,10 +198,16 @@ class ClaudeRunner:
198
198
  single_wake_timeout: float | None = None,
199
199
  partial_publisher: PartialPublisher | None = None,
200
200
  agent_to_session: "dict[str, str | None] | None" = None,
201
+ claude_permission_mode: str = "default",
201
202
  ) -> None:
202
203
  self.outbound = outbound
203
204
  self.workspace = workspace
204
205
  self.claude_binary = claude_binary
206
+ # Claude CLI ``--permission-mode``. "default" → flag omitted
207
+ # (legacy behaviour: only the cloudagent MCP toolset is
208
+ # whitelisted via --allowedTools, Bash/Write/Edit gate). See
209
+ # DaemonConfig.claude_permission_mode for the per-mode contract.
210
+ self.claude_permission_mode = claude_permission_mode
205
211
  # Allow tests to override the 90-min timeout without monkey-patching
206
212
  # a class attribute. Defaults to spec value.
207
213
  self._single_wake_timeout: float = (
@@ -541,7 +547,16 @@ The daemon discards stdout — only the mcp tool call reaches the user.
541
547
  write are intentionally LEFT to the caller so they always
542
548
  reflect the *last* attempt.
543
549
  """
544
- cmd = self._build_cmd(ws, agent_id, with_resume=with_resume)
550
+ raw_append = payload.get("append_system_prompt")
551
+ append_system_prompt = (
552
+ raw_append if isinstance(raw_append, str) and raw_append.strip() else None
553
+ )
554
+ cmd = self._build_cmd(
555
+ ws,
556
+ agent_id,
557
+ with_resume=with_resume,
558
+ append_system_prompt=append_system_prompt,
559
+ )
545
560
 
546
561
  # Task #118 (2026-05-12) — spawn cwd is now the agent-specific
547
562
  # cwd (per-agent override → host default → platform fallback).
@@ -773,12 +788,20 @@ Available cloudagent tools:
773
788
  agent_id: str,
774
789
  *,
775
790
  with_resume: bool = True,
791
+ append_system_prompt: str | None = None,
776
792
  ) -> list[str]:
777
793
  """Build the ``claude`` argv list for one wake.
778
794
 
779
795
  1k followup — ``with_resume=False`` forces the runner to omit
780
796
  ``--resume`` even if a stored ``last_session_id`` exists. The
781
797
  retry path uses this after a stale-session crash.
798
+
799
+ ``append_system_prompt`` is per-agent free-text (chairman-editable
800
+ via the agent dialog, stored in ``agents.metadata_['cloudagent:
801
+ append_system_prompt']`` and propagated through the deliver
802
+ payload). When non-empty it is appended verbatim to the operational
803
+ contract footer with a blank-line separator so it shows up at the
804
+ very tail of ``--append-system-prompt``.
782
805
  """
783
806
  cmd: list[str] = [self.claude_binary]
784
807
  last_session = ws.last_session_id() if with_resume else None
@@ -786,6 +809,8 @@ Available cloudagent tools:
786
809
  cmd += ["--resume", last_session]
787
810
  # 1i Bug F — inject operational contract via --append-system-prompt.
788
811
  system_prompt = self.SYSTEM_PROMPT_APPEND.format(agent_id=agent_id)
812
+ if append_system_prompt and append_system_prompt.strip():
813
+ system_prompt = f"{system_prompt}\n\n{append_system_prompt.strip()}"
789
814
  cmd += [
790
815
  "--mcp-config",
791
816
  str(ws.mcp_config_path()),
@@ -810,6 +835,16 @@ Available cloudagent tools:
810
835
  "--append-system-prompt",
811
836
  system_prompt,
812
837
  ]
838
+ # 2026-05-19 — opt-in permission relaxation for staff that must
839
+ # touch the filesystem / run build commands. "default" keeps the
840
+ # legacy headless behaviour (flag omitted; only the cloudagent
841
+ # MCP toolset runs unattended). "acceptEdits" / "bypassPermissions"
842
+ # come from DaemonConfig.claude_permission_mode. The spawn cwd is
843
+ # already pinned to the agent's workspace_root (see
844
+ # AgentWorkspaceLike.cwd / Task #118), so acceptEdits' auto-accept
845
+ # of file edits is naturally scoped to that working directory.
846
+ if self.claude_permission_mode != "default":
847
+ cmd += ["--permission-mode", self.claude_permission_mode]
813
848
  return cmd
814
849
 
815
850
  async def _consume_stdout(
@@ -74,6 +74,32 @@ def _resolve_cli_profile(profile: str | None) -> str:
74
74
  raise typer.Exit(2)
75
75
 
76
76
 
77
+ #: Env var that overrides ``DaemonConfig.claude_permission_mode``. The
78
+ #: only permission knob available to one-shot mode (no config.json).
79
+ PERMISSION_MODE_ENV = "CLOUDAGENT_DAEMON_PERMISSION_MODE"
80
+
81
+
82
+ def _apply_permission_mode_override(cfg): # type: ignore[no-untyped-def]
83
+ """Return ``cfg`` with ``claude_permission_mode`` replaced by the
84
+ ``CLOUDAGENT_DAEMON_PERMISSION_MODE`` env var when it is set.
85
+
86
+ The replacement goes through ``DaemonConfig`` construction so the
87
+ field_validator rejects unknown values (raised as ``ValueError``).
88
+ When the env var is unset / empty, ``cfg`` is returned unchanged.
89
+ """
90
+ from .config import DaemonConfig
91
+
92
+ mode = os.environ.get(PERMISSION_MODE_ENV)
93
+ if not mode:
94
+ return cfg
95
+ return DaemonConfig(
96
+ server_url=cfg.server_url,
97
+ machine_api_key=cfg.machine_api_key,
98
+ host_id=cfg.host_id,
99
+ claude_permission_mode=mode,
100
+ )
101
+
102
+
77
103
  @app.command()
78
104
  def login(
79
105
  server: str = typer.Option(..., "--server", help="Backend server URL."),
@@ -265,6 +291,19 @@ def start(
265
291
  )
266
292
  raise typer.Exit(1)
267
293
 
294
+ # 2026-05-19 — CLOUDAGENT_DAEMON_PERMISSION_MODE env var overrides
295
+ # config.json's ``claude_permission_mode``. This is the ONLY knob
296
+ # available to one-shot mode (``start --server-url --api-key``),
297
+ # which never reads or writes a config.json.
298
+ try:
299
+ cfg = _apply_permission_mode_override(cfg)
300
+ except ValueError as exc:
301
+ typer.echo(
302
+ f"Error: invalid CLOUDAGENT_DAEMON_PERMISSION_MODE — {exc}",
303
+ err=True,
304
+ )
305
+ raise typer.Exit(2)
306
+
268
307
  state = (
269
308
  DaemonState.load_default(profile_name)
270
309
  if DaemonState.default_path(profile_name).exists()
@@ -15,7 +15,7 @@ import tempfile
15
15
  from pathlib import Path
16
16
  from typing import Self
17
17
 
18
- from pydantic import BaseModel
18
+ from pydantic import BaseModel, field_validator
19
19
 
20
20
  from .paths import (
21
21
  DEFAULT_PROFILE,
@@ -51,6 +51,44 @@ class DaemonConfig(BaseModel):
51
51
  machine_api_key: str
52
52
  host_id: str
53
53
 
54
+ # Claude CLI permission mode for daemon-spawned staff agents.
55
+ # The daemon spawns each staff's ``claude`` subprocess with
56
+ # ``cwd`` already pinned to that agent's workspace_root (see
57
+ # AgentWorkspaceLike.cwd / Task #118), so the mode's effect is
58
+ # naturally scoped to the agent's project directory.
59
+ #
60
+ # "default" — (safe default) only the cloudagent MCP
61
+ # toolset runs unattended; Bash/Write/Edit
62
+ # prompt for permission, which in headless
63
+ # ``--print`` mode means they silently fail.
64
+ # Staff cannot touch the filesystem.
65
+ # "acceptEdits" — file edits (Write/Edit/MultiEdit/Notebook-
66
+ # Edit) are auto-accepted **within the
67
+ # workspace (cwd)** — edits outside the
68
+ # working directory still gate. Bash still
69
+ # prompts (so git commit / build stay
70
+ # blocked). This is "只在当前工作目录
71
+ # bypass 文件操作".
72
+ # "bypassPermissions" — every tool runs unattended. Required for
73
+ # staff that must run Bash (git commit,
74
+ # pipeline, build). Bash commands have no
75
+ # directory hard-boundary — a command body
76
+ # can cd out — so only enable this on a
77
+ # machine you are comfortable letting the
78
+ # staff agent operate.
79
+ claude_permission_mode: str = "default"
80
+
81
+ @field_validator("claude_permission_mode")
82
+ @classmethod
83
+ def _validate_permission_mode(cls, v: str) -> str:
84
+ allowed = {"default", "acceptEdits", "bypassPermissions"}
85
+ if v not in allowed:
86
+ raise ValueError(
87
+ f"claude_permission_mode must be one of {sorted(allowed)}, "
88
+ f"got {v!r}"
89
+ )
90
+ return v
91
+
54
92
  # ------------------------------------------------------------------
55
93
  # Path helpers
56
94
  # ------------------------------------------------------------------
@@ -454,6 +454,7 @@ async def _run_sse_post(
454
454
  claude_binary=claude_binary,
455
455
  partial_publisher=_publish_partial,
456
456
  agent_to_session=_agent_to_session,
457
+ claude_permission_mode=cfg.claude_permission_mode,
457
458
  )
458
459
 
459
460
  async def _queue_full(agent_id: str, message_id: str) -> None:
@@ -737,7 +738,10 @@ async def _run_legacy_ws(
737
738
  outbound = OutboundQueue(transport=None, pending_tool_calls=pending_tool_calls)
738
739
 
739
740
  runner = ClaudeRunner(
740
- outbound=outbound, workspace=workspace, claude_binary=claude_binary
741
+ outbound=outbound,
742
+ workspace=workspace,
743
+ claude_binary=claude_binary,
744
+ claude_permission_mode=cfg.claude_permission_mode,
741
745
  )
742
746
 
743
747
  async def _queue_full(agent_id: str, message_id: str) -> None:
@@ -1 +0,0 @@
1
- __version__ = "0.2.1"