cloudagent-daemon 0.5.0__tar.gz → 0.5.1__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.
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/CHANGELOG.md +31 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/PKG-INFO +1 -1
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/pyproject.toml +1 -1
- cloudagent_daemon-0.5.1/src/cloudagent_daemon/__init__.py +1 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/claude_runner.py +84 -11
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/cli.py +11 -1
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/mcp_bridge.py +46 -2
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/paths.py +3 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/test_mcp_bridge_round_trip.py +66 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_claude_runner.py +65 -12
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_cli_smoke.py +1 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_paths.py +7 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/uv.lock +1 -1
- cloudagent_daemon-0.5.0/src/cloudagent_daemon/__init__.py +0 -1
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/.gitignore +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/LICENSE +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/README.md +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/scripts/release.sh +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/__main__.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/agent_download.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/agent_upload.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/agent_upload_denylist.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/auth_preflight.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/config.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/context_fetch.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/delegation_report.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/doctor.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/frame_dispatch.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/http_outbound.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/injection_state.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/jsonl_logging.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/login.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/main_loop.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/metrics.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/metrics_server.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/outbound_queue.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/protocol.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/qoder_credentials.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/readonly_bash_guard.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/runtime_config.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/runtime_detect.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/service.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/spawn_outbox.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/spawn_queue.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/sse_client.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/transport.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/unix_socket.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/workspace.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/ws_client.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/__init__.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/_fake_sse_server.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/conftest.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/test_e2e_deliver_to_spawn.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/test_e2e_sse.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/test_full_e2e.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/test_one_shot_cli.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/test_spawn_mcp_bridge_e2e.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/test_sse_handshake.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/test_sse_watchdog.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/test_ws_handshake.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/test_ws_reconnect.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/conftest.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_agent_download.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_agent_upload.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_auth_preflight.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_claude_runner_codex_contract.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_claude_runner_routing_contract.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_claude_runner_w3.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_cli_multi_profile.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_config.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_context_fetch.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_delegation_report.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_doctor.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_frame_dispatch.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_http_outbound.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_injection_state.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_jsonl_logging.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_login.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_main_loop_assign.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_main_loop_preflight.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_mcp_bridge_agent_retrieval.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_mcp_bridge_send_message.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_metrics.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_metrics_server.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_multi_tool_wake.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_outbound_queue.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_outbox_stuck_watchdog.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_packaging_mcp_pin.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_protocol.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_qoder_credentials.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_readonly_bash_guard.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_runtime_detect.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_service.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_service_multi_profile.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_spawn_outbox.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_spawn_queue.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_sse_active_watchdog.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_start_guard.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_status_stop.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_workspace.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_workspace_root_task113.py +0 -0
- {cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_ws_client_insecure.py +0 -0
|
@@ -4,6 +4,37 @@ All notable changes to the local daemon. Format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/); versions are
|
|
5
5
|
[PEP 440](https://peps.python.org/pep-0440/). Release with `scripts/release.sh`.
|
|
6
6
|
|
|
7
|
+
## [0.5.1] — 2026-08-20
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Qoder engine now supports cross-owner `read_analyze` (only-read remote
|
|
11
|
+
command). Previously the daemon fail-closed refused `read_analyze` on qoder
|
|
12
|
+
because qodercli's tool-restriction was never verified end-to-end (#106).
|
|
13
|
+
This release ships a proven limited-tool surface:
|
|
14
|
+
- qodercli launched with `--setting-sources '' --permission-mode
|
|
15
|
+
bypass_permissions --tools Read Glob Grep --no-session-persistence
|
|
16
|
+
--strict-mcp-config` and a dedicated read_analyze MCP config; no `--resume`
|
|
17
|
+
of the owner lane.
|
|
18
|
+
- `mcp-bridge --tool-profile read_analyze` applies a POSITIVE allowlist at
|
|
19
|
+
MCP `tools/list` time: only 13 membership-gated query tools remain; every
|
|
20
|
+
write/mutation tool (`platform_send`, `room_post`, `send_message`,
|
|
21
|
+
upload/download, reminder/task mutation, react, login, chief tools) is
|
|
22
|
+
removed. Unknown profile → `ValueError` (fail-closed).
|
|
23
|
+
- Final analysis is delivered by the daemon-controlled fallback to the
|
|
24
|
+
backend-supplied origin conversation only; the model cannot choose a
|
|
25
|
+
destination.
|
|
26
|
+
Verified by real cross-owner E2E (human non-owner → qoder agent, induced
|
|
27
|
+
write/command/`platform_send` all blocked, read-only result returned to the
|
|
28
|
+
origin channel). Confidentiality scope is intentionally the target agent's
|
|
29
|
+
own read ACL (same-org cross-owner read/analysis is the product contract).
|
|
30
|
+
|
|
31
|
+
### Known debt
|
|
32
|
+
- mcp-bridge per-spawn token-file is still not wired end-to-end; bridge/daemon
|
|
33
|
+
IPC trust rests on the same-user 0600 unix socket, so a malicious native
|
|
34
|
+
process under the same OS user could forge an agent_id. Pre-existing and
|
|
35
|
+
shared by all engines/run-modes; tracked separately (#110), not introduced
|
|
36
|
+
by this release.
|
|
37
|
+
|
|
7
38
|
## [0.5.0] — 2026-08-19
|
|
8
39
|
|
|
9
40
|
First release since 0.4.1 (2026-07-22). Everything below shipped to internal
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cloudagent-daemon
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Local daemon for cloudagent — bridges Claude Code, Codex, and Qoder CLI agents to the cloudagent backend.
|
|
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.5.
|
|
7
|
+
version = "0.5.1"
|
|
8
8
|
description = "Local daemon for cloudagent — bridges Claude Code, Codex, and Qoder CLI agents to the cloudagent backend."
|
|
9
9
|
authors = [{name = "chaoyin", email = "tudoucyx@gmail.com"}]
|
|
10
10
|
readme = "README.md"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.1"
|
|
@@ -1382,6 +1382,9 @@ The daemon discards stdout — only the mcp tool call reaches the channel.
|
|
|
1382
1382
|
wake_prompt = self._codex_stdin_preamble(
|
|
1383
1383
|
agent_id, append_system_prompt,
|
|
1384
1384
|
read_analyze=run_mode == "read_analyze",
|
|
1385
|
+
daemon_managed_reply=(
|
|
1386
|
+
runtime_engine == "qoder" and run_mode == "read_analyze"
|
|
1387
|
+
),
|
|
1385
1388
|
) + wake_prompt
|
|
1386
1389
|
try:
|
|
1387
1390
|
assert proc.stdin is not None # for type checkers
|
|
@@ -1519,6 +1522,19 @@ The daemon discards stdout — only the mcp tool call reaches the channel.
|
|
|
1519
1522
|
"mcp__cloudagent__platform_task_status",
|
|
1520
1523
|
)
|
|
1521
1524
|
|
|
1525
|
+
# #107 — qoder read_analyze is enforced with a positive allowlist, not a
|
|
1526
|
+
# denylist. qodercli 1.1.26 live probes showed that ``--tools`` removes
|
|
1527
|
+
# every other built-in capability from the init event even under
|
|
1528
|
+
# ``bypass_permissions``; the latter is still required so the injected
|
|
1529
|
+
# cloudagent MCP server can post the analysis result in headless mode.
|
|
1530
|
+
# Keep this deliberately small: WebFetch/WebSearch are excluded to avoid
|
|
1531
|
+
# an untrusted cross-owner prompt exfiltrating files it just read.
|
|
1532
|
+
QODER_READ_ANALYZE_BUILTIN_TOOLS: tuple[str, ...] = (
|
|
1533
|
+
"Read",
|
|
1534
|
+
"Glob",
|
|
1535
|
+
"Grep",
|
|
1536
|
+
)
|
|
1537
|
+
|
|
1522
1538
|
# 1i Bug F — operational contract for daemon-spawned wakes.
|
|
1523
1539
|
# Without this, claude in --print mode just dumps its answer to stdout
|
|
1524
1540
|
# and exits, never invoking send_message — so the user never sees a reply.
|
|
@@ -1875,10 +1891,16 @@ TASKS (频道任务板) — 频道里的分工协作走任务认领原语,纪
|
|
|
1875
1891
|
"提交给 owner 审核 —— owner 批准后再由 owner 指挥自己的 agent 实际落盘。"
|
|
1876
1892
|
"不要试图直接改文件;被拦不是故障,是权限设计。"
|
|
1877
1893
|
)
|
|
1894
|
+
QODER_READ_ANALYZE_REPLY_NOTE = (
|
|
1895
|
+
"【Qoder 只读回报例外】本模式刻意不向模型暴露 platform_send、"
|
|
1896
|
+
"room_post、send_message 或任何写类 MCP 工具。不要尝试调用这些工具;"
|
|
1897
|
+
"请把完整分析作为本轮最终文本直接输出。daemon 会把该最终文本经受控"
|
|
1898
|
+
"fallback 发回 backend 下发的原始 conversation_id,目标不可由你更改。"
|
|
1899
|
+
)
|
|
1878
1900
|
|
|
1879
1901
|
def _codex_stdin_preamble(
|
|
1880
1902
|
self, agent_id: str, append_system_prompt: str | None,
|
|
1881
|
-
*, read_analyze: bool = False,
|
|
1903
|
+
*, read_analyze: bool = False, daemon_managed_reply: bool = False,
|
|
1882
1904
|
) -> str:
|
|
1883
1905
|
"""The system contract as a stdin preamble — codex's substitute for
|
|
1884
1906
|
``--append-system-prompt`` (which only the claude argv path has).
|
|
@@ -1891,6 +1913,8 @@ TASKS (频道任务板) — 频道里的分工协作走任务认领原语,纪
|
|
|
1891
1913
|
parts = [self.SYSTEM_PROMPT_APPEND.format(agent_id=agent_id)]
|
|
1892
1914
|
if read_analyze:
|
|
1893
1915
|
parts.append(self.READ_ANALYZE_SYSTEM_NOTE)
|
|
1916
|
+
if daemon_managed_reply:
|
|
1917
|
+
parts.append(self.QODER_READ_ANALYZE_REPLY_NOTE)
|
|
1894
1918
|
if append_system_prompt and append_system_prompt.strip():
|
|
1895
1919
|
parts.append(append_system_prompt.strip())
|
|
1896
1920
|
joined = "\n\n".join(parts)
|
|
@@ -1952,6 +1976,38 @@ TASKS (频道任务板) — 频道里的分工协作走任务认领原语,纪
|
|
|
1952
1976
|
exc_info=True,
|
|
1953
1977
|
)
|
|
1954
1978
|
|
|
1979
|
+
@staticmethod
|
|
1980
|
+
def _qoder_read_analyze_mcp_config(
|
|
1981
|
+
ws: AgentWorkspaceLike, agent_id: str
|
|
1982
|
+
) -> Path:
|
|
1983
|
+
"""Write the per-wake, query-only cloudagent MCP config for qoder.
|
|
1984
|
+
|
|
1985
|
+
The regular workspace config exposes state-changing tools (send,
|
|
1986
|
+
upload, task mutations, reminders). ``--tools`` only restricts qoder
|
|
1987
|
+
built-ins, so read_analyze needs a separately profiled bridge whose
|
|
1988
|
+
``tools/list`` contains query-only operations. The daemon fallback,
|
|
1989
|
+
not the model, posts the final analysis back to the source channel.
|
|
1990
|
+
"""
|
|
1991
|
+
from .paths import build_mcp_bridge_args, daemon_executable
|
|
1992
|
+
|
|
1993
|
+
config_path = ws.path / ".meta" / "qoder-read-analyze-mcp-config.json"
|
|
1994
|
+
token_file = str(ws.path / ".meta" / "claude-token.txt")
|
|
1995
|
+
config = {
|
|
1996
|
+
"mcpServers": {
|
|
1997
|
+
"cloudagent": {
|
|
1998
|
+
"command": daemon_executable(),
|
|
1999
|
+
"args": build_mcp_bridge_args(
|
|
2000
|
+
agent_id,
|
|
2001
|
+
token_file,
|
|
2002
|
+
tool_profile="read_analyze",
|
|
2003
|
+
),
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
2008
|
+
config_path.write_text(json.dumps(config, indent=2), encoding="utf-8")
|
|
2009
|
+
return config_path
|
|
2010
|
+
|
|
1955
2011
|
def _build_cmd(
|
|
1956
2012
|
self,
|
|
1957
2013
|
ws: AgentWorkspaceLike,
|
|
@@ -2061,20 +2117,14 @@ TASKS (频道任务板) — 频道里的分工协作走任务认领原语,纪
|
|
|
2061
2117
|
# stream-json/--append-system-prompt/--mcp-config/--resume 全对齐,
|
|
2062
2118
|
# 输出走既有 stream_json 消费器(联调时如发现事件形状偏差再适配)。
|
|
2063
2119
|
# 认证:QODER_PERSONAL_ACCESS_TOKEN 环境变量或 qodercli login。
|
|
2064
|
-
if read_analyze:
|
|
2065
|
-
# fail-closed:qoder 的只读硬化(--tools/--disallowed-tools 的
|
|
2066
|
-
# 内建工具名、--permission-mode 无头语义)尚未在真机验证 ——
|
|
2067
|
-
# 未验证的安全属性不许上线(#55 口径),宁可拒绝 spawn。
|
|
2068
|
-
raise RuntimeError(
|
|
2069
|
-
"qoder runtime does not support run_mode=read_analyze yet "
|
|
2070
|
-
"(read-only hardening flags unverified); refusing to spawn"
|
|
2071
|
-
)
|
|
2072
2120
|
binary = self.runtime_binaries.get("qoder") or "qodercli"
|
|
2073
2121
|
# #95 — 会话按 conversation_id 分轨(DM/各频道各自续聊),不再共用
|
|
2074
2122
|
# 全局 last_session;无 conversation_id 的非平台唤醒沿用全局轨。
|
|
2123
|
+
# #107 — read_analyze 与 claude/codex 一样绝不 resume owner 轨,
|
|
2124
|
+
# 防外部指挥上下文渗进后续 full 会话。
|
|
2075
2125
|
lane_session = (
|
|
2076
2126
|
self._qoder_lane_session(ws, conversation_id)
|
|
2077
|
-
if with_resume else None
|
|
2127
|
+
if with_resume and not read_analyze else None
|
|
2078
2128
|
)
|
|
2079
2129
|
cmd = [binary]
|
|
2080
2130
|
# r6 同款:模型 + 推理强度由 chairman 在编辑页选(qoder 档位
|
|
@@ -2085,13 +2135,18 @@ TASKS (频道任务板) — 频道里的分工协作走任务认领原语,纪
|
|
|
2085
2135
|
cmd += ["--reasoning-effort", reasoning_effort]
|
|
2086
2136
|
if lane_session:
|
|
2087
2137
|
cmd += ["--resume", lane_session]
|
|
2138
|
+
mcp_config_path = (
|
|
2139
|
+
self._qoder_read_analyze_mcp_config(ws, agent_id)
|
|
2140
|
+
if read_analyze
|
|
2141
|
+
else ws.mcp_config_path()
|
|
2142
|
+
)
|
|
2088
2143
|
# #95 — 运营契约 + 本轮 append(含频道级提示)**不走 argv
|
|
2089
2144
|
# --append-system-prompt**:qodercli --resume 不可靠采用新的 append,
|
|
2090
2145
|
# 改与 codex 同款折进 stdin 活跃前导(见 _spawn_attempt),每轮必到
|
|
2091
2146
|
# 模型眼前,不依赖 CLI 对 resume 的语义。
|
|
2092
2147
|
cmd += [
|
|
2093
2148
|
"--mcp-config",
|
|
2094
|
-
str(
|
|
2149
|
+
str(mcp_config_path),
|
|
2095
2150
|
"--print",
|
|
2096
2151
|
"--output-format",
|
|
2097
2152
|
"stream-json",
|
|
@@ -2100,6 +2155,24 @@ TASKS (频道任务板) — 频道里的分工协作走任务认领原语,纪
|
|
|
2100
2155
|
"--permission-mode",
|
|
2101
2156
|
"bypass_permissions",
|
|
2102
2157
|
]
|
|
2158
|
+
if read_analyze:
|
|
2159
|
+
# #107 — qodercli 1.1.26 双机真测:--tools 是 built-in
|
|
2160
|
+
# capability allowlist;在 bypass_permissions 下 init.tools
|
|
2161
|
+
# 仍精确为 Glob/Grep/Read,Bash/Write/Edit/NotebookEdit/Web*
|
|
2162
|
+
# 不可调用,诱导写文件的探针保持不存在。同时 bypass 让
|
|
2163
|
+
# cloudagent MCP 能在 --print 无头模式回报结果。
|
|
2164
|
+
#
|
|
2165
|
+
# 空 setting-sources 不加载 user/project/local hooks;strict
|
|
2166
|
+
# MCP 只保留 daemon 生成的 cloudagent 配置;禁用 session
|
|
2167
|
+
# persistence,配合上面的 no-resume 保证 owner 会话隔离。
|
|
2168
|
+
cmd += [
|
|
2169
|
+
"--setting-sources",
|
|
2170
|
+
"",
|
|
2171
|
+
"--no-session-persistence",
|
|
2172
|
+
"--strict-mcp-config",
|
|
2173
|
+
"--tools",
|
|
2174
|
+
*self.QODER_READ_ANALYZE_BUILTIN_TOOLS,
|
|
2175
|
+
]
|
|
2103
2176
|
return cmd
|
|
2104
2177
|
|
|
2105
2178
|
cmd = [self.runtime_binaries.get("claude_code") or self.claude_binary]
|
|
@@ -973,13 +973,23 @@ def mcp_bridge(
|
|
|
973
973
|
),
|
|
974
974
|
),
|
|
975
975
|
profile: str | None = typer.Option(None, "--profile", help=_PROFILE_HELP),
|
|
976
|
+
tool_profile: str = typer.Option(
|
|
977
|
+
"full",
|
|
978
|
+
"--tool-profile",
|
|
979
|
+
help="MCP capability profile (full or read_analyze).",
|
|
980
|
+
),
|
|
976
981
|
) -> None:
|
|
977
982
|
"""[internal] stdio MCP server invoked by claude subprocess."""
|
|
978
983
|
from .mcp_bridge import run_bridge
|
|
979
984
|
|
|
980
985
|
profile_name = _resolve_cli_profile(profile)
|
|
981
986
|
asyncio.run(
|
|
982
|
-
run_bridge(
|
|
987
|
+
run_bridge(
|
|
988
|
+
agent_id=agent_id,
|
|
989
|
+
token_file=token_file,
|
|
990
|
+
profile=profile_name,
|
|
991
|
+
tool_profile=tool_profile,
|
|
992
|
+
)
|
|
983
993
|
)
|
|
984
994
|
|
|
985
995
|
|
|
@@ -79,6 +79,32 @@ PLATFORM_TOOLS: frozenset[str] = frozenset(
|
|
|
79
79
|
}
|
|
80
80
|
)
|
|
81
81
|
|
|
82
|
+
# #107 — cross-owner ``read_analyze`` must expose no state-changing MCP
|
|
83
|
+
# capability to the model. The daemon posts the final analysis through its
|
|
84
|
+
# controlled platform fallback, so ``platform_send`` is intentionally absent;
|
|
85
|
+
# only membership-gated/query-only tools remain visible inside qodercli.
|
|
86
|
+
# Confidentiality scope is intentionally the TARGET AGENT'S existing read ACL,
|
|
87
|
+
# not the initiating human's ACL: same-org cross-owner read/analysis is the
|
|
88
|
+
# product contract. Results can leave that read scope only through the daemon
|
|
89
|
+
# fallback, whose destination is the backend-supplied source conversation.
|
|
90
|
+
READ_ANALYZE_MCP_TOOLS: frozenset[str] = frozenset(
|
|
91
|
+
{
|
|
92
|
+
"list_messages",
|
|
93
|
+
"list_reminders",
|
|
94
|
+
"platform_read",
|
|
95
|
+
"platform_channels_list",
|
|
96
|
+
"platform_search",
|
|
97
|
+
"platform_react_list",
|
|
98
|
+
"platform_inbox",
|
|
99
|
+
"platform_list_integrations",
|
|
100
|
+
"platform_task_list",
|
|
101
|
+
"search_task_context",
|
|
102
|
+
"read_task_room_context",
|
|
103
|
+
"resolve_task_ref",
|
|
104
|
+
"list_related_delegations",
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
|
|
82
108
|
|
|
83
109
|
def _read_identity(agent_id: str) -> dict:
|
|
84
110
|
"""Defensive read of ``~/.cloudagent/agents/<id>/.meta/identity.json``.
|
|
@@ -206,8 +232,9 @@ def build_server(
|
|
|
206
232
|
socket_path: Path = DEFAULT_SOCKET_PATH,
|
|
207
233
|
identity: dict | None = None,
|
|
208
234
|
session_id: str | None = None,
|
|
235
|
+
tool_profile: str = "full",
|
|
209
236
|
) -> FastMCP:
|
|
210
|
-
"""Construct a FastMCP server
|
|
237
|
+
"""Construct a FastMCP server filtered by identity and tool profile.
|
|
211
238
|
|
|
212
239
|
Split out from ``run_bridge`` so unit tests can introspect the tool
|
|
213
240
|
list without spinning up stdio.
|
|
@@ -226,7 +253,15 @@ def build_server(
|
|
|
226
253
|
Pass ``None`` only when no session is known (the backend will then
|
|
227
254
|
reject with INVALID_ARGS — that's the expected behaviour and
|
|
228
255
|
surfaces the bug rather than masking it).
|
|
256
|
+
|
|
257
|
+
``tool_profile='read_analyze'`` is the fail-closed cross-owner surface:
|
|
258
|
+
after normal registration, every tool outside
|
|
259
|
+
:data:`READ_ANALYZE_MCP_TOOLS` is removed. Filtering at MCP registration
|
|
260
|
+
time is stronger than asking the model not to call a write tool: the tool
|
|
261
|
+
never appears in ``tools/list`` and cannot be invoked by name.
|
|
229
262
|
"""
|
|
263
|
+
if tool_profile not in ("full", "read_analyze"):
|
|
264
|
+
raise ValueError(f"unknown MCP tool profile: {tool_profile}")
|
|
230
265
|
if identity is None:
|
|
231
266
|
identity = _read_identity(agent_id)
|
|
232
267
|
|
|
@@ -791,6 +826,11 @@ def build_server(
|
|
|
791
826
|
session_id,
|
|
792
827
|
)
|
|
793
828
|
|
|
829
|
+
if tool_profile == "read_analyze":
|
|
830
|
+
for tool in tuple(server._tool_manager.list_tools()):
|
|
831
|
+
if tool.name not in READ_ANALYZE_MCP_TOOLS:
|
|
832
|
+
server.remove_tool(tool.name)
|
|
833
|
+
|
|
794
834
|
return server
|
|
795
835
|
|
|
796
836
|
|
|
@@ -825,6 +865,7 @@ async def run_bridge(
|
|
|
825
865
|
token_file: Path | None = None,
|
|
826
866
|
socket_path: Path | None = None,
|
|
827
867
|
profile: str | None = None,
|
|
868
|
+
tool_profile: str = "full",
|
|
828
869
|
) -> None:
|
|
829
870
|
"""Run the bridge until claude closes stdin.
|
|
830
871
|
|
|
@@ -910,10 +951,13 @@ async def run_bridge(
|
|
|
910
951
|
socket_path=socket_path,
|
|
911
952
|
identity=identity,
|
|
912
953
|
session_id=current_session_id,
|
|
954
|
+
tool_profile=tool_profile,
|
|
913
955
|
)
|
|
914
956
|
logger.info(
|
|
915
|
-
"mcp_bridge: ready agent=%s is_chief=%s —
|
|
957
|
+
"mcp_bridge: ready agent=%s is_chief=%s tool_profile=%s — "
|
|
958
|
+
"entering stdio loop",
|
|
916
959
|
agent_id,
|
|
917
960
|
identity.get("is_chief") is True,
|
|
961
|
+
tool_profile,
|
|
918
962
|
)
|
|
919
963
|
await server.run_stdio_async()
|
|
@@ -200,6 +200,7 @@ def build_mcp_bridge_args(
|
|
|
200
200
|
agent_id: str,
|
|
201
201
|
token_file: str,
|
|
202
202
|
profile: str | None = None,
|
|
203
|
+
tool_profile: str = "full",
|
|
203
204
|
) -> list[str]:
|
|
204
205
|
"""Build argv for the ``cloudagent-daemon mcp-bridge`` MCP server.
|
|
205
206
|
|
|
@@ -222,6 +223,8 @@ def build_mcp_bridge_args(
|
|
|
222
223
|
args = ["mcp-bridge", "--agent-id", agent_id, "--token-file", token_file]
|
|
223
224
|
if resolved != DEFAULT_PROFILE:
|
|
224
225
|
args += ["--profile", resolved]
|
|
226
|
+
if tool_profile != "full":
|
|
227
|
+
args += ["--tool-profile", tool_profile]
|
|
225
228
|
return args
|
|
226
229
|
|
|
227
230
|
|
{cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/test_mcp_bridge_round_trip.py
RENAMED
|
@@ -204,7 +204,10 @@ _COMMON_TOOLS = {
|
|
|
204
204
|
"send_message",
|
|
205
205
|
"list_messages",
|
|
206
206
|
"upload_file",
|
|
207
|
+
"download_attachment",
|
|
207
208
|
"schedule_reminder",
|
|
209
|
+
"list_reminders",
|
|
210
|
+
"cancel_reminder",
|
|
208
211
|
"room_post",
|
|
209
212
|
}
|
|
210
213
|
_RETRIEVAL_TOOLS = {
|
|
@@ -219,6 +222,8 @@ _RETRIEVAL_TOOLS = {
|
|
|
219
222
|
_PLATFORM_TOOLS = {
|
|
220
223
|
"platform_send",
|
|
221
224
|
"platform_read",
|
|
225
|
+
"platform_channels_list",
|
|
226
|
+
"platform_search",
|
|
222
227
|
"platform_react",
|
|
223
228
|
"platform_react_list",
|
|
224
229
|
"platform_inbox",
|
|
@@ -281,6 +286,67 @@ async def test_missing_is_chief_defaults_to_staff() -> None:
|
|
|
281
286
|
assert names.isdisjoint(_CHIEF_ONLY_TOOLS)
|
|
282
287
|
|
|
283
288
|
|
|
289
|
+
async def test_read_analyze_profile_registers_query_only_tools() -> None:
|
|
290
|
+
"""#107 — cross-owner qoder never sees state-changing MCP tools.
|
|
291
|
+
|
|
292
|
+
The final reply is posted by the daemon's controlled fallback, so even
|
|
293
|
+
platform_send is intentionally absent from the model-visible surface.
|
|
294
|
+
"""
|
|
295
|
+
server = build_server(
|
|
296
|
+
agent_id="a_read_analyze",
|
|
297
|
+
socket_path=Path("/tmp/never-used.sock"),
|
|
298
|
+
identity={"is_chief": True},
|
|
299
|
+
tool_profile="read_analyze",
|
|
300
|
+
)
|
|
301
|
+
names = {t.name for t in await server.list_tools()}
|
|
302
|
+
assert names == {
|
|
303
|
+
"list_messages",
|
|
304
|
+
"list_reminders",
|
|
305
|
+
"platform_read",
|
|
306
|
+
"platform_channels_list",
|
|
307
|
+
"platform_search",
|
|
308
|
+
"platform_react_list",
|
|
309
|
+
"platform_inbox",
|
|
310
|
+
"platform_list_integrations",
|
|
311
|
+
"platform_task_list",
|
|
312
|
+
"search_task_context",
|
|
313
|
+
"read_task_room_context",
|
|
314
|
+
"resolve_task_ref",
|
|
315
|
+
"list_related_delegations",
|
|
316
|
+
}
|
|
317
|
+
assert names.isdisjoint(
|
|
318
|
+
{
|
|
319
|
+
"send_message",
|
|
320
|
+
"upload_file",
|
|
321
|
+
"download_attachment",
|
|
322
|
+
"schedule_reminder",
|
|
323
|
+
"cancel_reminder",
|
|
324
|
+
"room_post",
|
|
325
|
+
"platform_send",
|
|
326
|
+
"platform_react",
|
|
327
|
+
"platform_login",
|
|
328
|
+
"platform_task_create",
|
|
329
|
+
"platform_task_claim",
|
|
330
|
+
"platform_task_release",
|
|
331
|
+
"platform_task_status",
|
|
332
|
+
"delegate_to_staff",
|
|
333
|
+
"close_task",
|
|
334
|
+
"request_user_approval",
|
|
335
|
+
"set_company_status",
|
|
336
|
+
}
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
async def test_unknown_tool_profile_fails_closed() -> None:
|
|
341
|
+
with pytest.raises(ValueError, match="unknown MCP tool profile"):
|
|
342
|
+
build_server(
|
|
343
|
+
agent_id="a_bad_profile",
|
|
344
|
+
socket_path=Path("/tmp/never-used.sock"),
|
|
345
|
+
identity={},
|
|
346
|
+
tool_profile="typo",
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
284
350
|
async def test_chief_only_tool_handler_actually_callable_when_chief(
|
|
285
351
|
ipc_server: tuple[Path, list[dict]],
|
|
286
352
|
) -> None:
|
|
@@ -3036,13 +3036,35 @@ async def test_qoder_runtime_resumes_stored_session(fake_claude, tmp_path):
|
|
|
3036
3036
|
assert argv[argv.index("--resume") + 1] == "qoder_old_7"
|
|
3037
3037
|
|
|
3038
3038
|
|
|
3039
|
-
async def
|
|
3040
|
-
|
|
3041
|
-
|
|
3039
|
+
async def test_qoder_runtime_read_analyze_uses_verified_tool_allowlist(
|
|
3040
|
+
fake_claude, tmp_path
|
|
3041
|
+
):
|
|
3042
|
+
"""#107 — verified qoder read_analyze keeps read tools + cloudagent MCP,
|
|
3043
|
+
drops every write/shell/network built-in, and never resumes an owner lane."""
|
|
3042
3044
|
mcp_cfg = tmp_path / "mcp.json"
|
|
3043
3045
|
mcp_cfg.write_text("{}")
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
+
(tmp_path / ".meta").mkdir()
|
|
3047
|
+
lanes_path = tmp_path / ".meta" / "qoder-sessions.json"
|
|
3048
|
+
lanes_path.write_text(json.dumps({"conv_A": "owner_lane"}))
|
|
3049
|
+
agent_ws = FakeAgentWorkspace(
|
|
3050
|
+
mcp_path=mcp_cfg, last_session="owner_global", path=tmp_path
|
|
3051
|
+
)
|
|
3052
|
+
ws = FakeWorkspace(agent_ws)
|
|
3053
|
+
fake = fake_claude(
|
|
3054
|
+
stdout_lines=[
|
|
3055
|
+
json.dumps({
|
|
3056
|
+
"type": "system",
|
|
3057
|
+
"subtype": "init",
|
|
3058
|
+
"session_id": "readonly_ephemeral",
|
|
3059
|
+
}),
|
|
3060
|
+
json.dumps({
|
|
3061
|
+
"type": "result",
|
|
3062
|
+
"subtype": "success",
|
|
3063
|
+
"result": "analysis",
|
|
3064
|
+
}),
|
|
3065
|
+
],
|
|
3066
|
+
exit_code=0,
|
|
3067
|
+
)
|
|
3046
3068
|
runner = ClaudeRunner(
|
|
3047
3069
|
outbound=FakeOutbound(),
|
|
3048
3070
|
workspace=ws,
|
|
@@ -3050,13 +3072,44 @@ async def test_qoder_runtime_refuses_read_analyze(fake_claude, tmp_path):
|
|
|
3050
3072
|
runtime_binaries={"qoder": fake},
|
|
3051
3073
|
single_wake_timeout=10.0,
|
|
3052
3074
|
)
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
runtime_engine
|
|
3058
|
-
run_mode
|
|
3059
|
-
|
|
3075
|
+
result = await runner.run_one(
|
|
3076
|
+
"agent-A",
|
|
3077
|
+
{
|
|
3078
|
+
**_make_payload(),
|
|
3079
|
+
"runtime_engine": "qoder",
|
|
3080
|
+
"run_mode": "read_analyze",
|
|
3081
|
+
"conversation_id": "conv_A",
|
|
3082
|
+
},
|
|
3083
|
+
)
|
|
3084
|
+
|
|
3085
|
+
assert result.captured_session_id == "readonly_ephemeral"
|
|
3086
|
+
argv = _read_argv(tmp_path)
|
|
3087
|
+
assert "--resume" not in argv
|
|
3088
|
+
assert argv[argv.index("--permission-mode") + 1] == "bypass_permissions"
|
|
3089
|
+
assert argv[argv.index("--setting-sources") + 1] == ""
|
|
3090
|
+
assert "--no-session-persistence" in argv
|
|
3091
|
+
assert "--strict-mcp-config" in argv
|
|
3092
|
+
assert argv[argv.index("--tools") + 1:] == ["Read", "Glob", "Grep"]
|
|
3093
|
+
for forbidden in (
|
|
3094
|
+
"Bash", "Write", "Edit", "NotebookEdit", "WebFetch", "WebSearch"
|
|
3095
|
+
):
|
|
3096
|
+
assert forbidden not in argv[argv.index("--tools") + 1:]
|
|
3097
|
+
|
|
3098
|
+
readonly_mcp_path = Path(argv[argv.index("--mcp-config") + 1])
|
|
3099
|
+
assert readonly_mcp_path != mcp_cfg
|
|
3100
|
+
readonly_mcp = json.loads(readonly_mcp_path.read_text())
|
|
3101
|
+
bridge_args = readonly_mcp["mcpServers"]["cloudagent"]["args"]
|
|
3102
|
+
assert bridge_args[-2:] == ["--tool-profile", "read_analyze"]
|
|
3103
|
+
|
|
3104
|
+
stdin = _read_stdin(tmp_path)
|
|
3105
|
+
assert "只读分析模式" in stdin
|
|
3106
|
+
assert "Qoder 只读回报例外" in stdin
|
|
3107
|
+
assert "daemon 会把该最终文本" in stdin
|
|
3108
|
+
# The read-only qoder session is neither resumed nor persisted into the
|
|
3109
|
+
# owner lane/global session track.
|
|
3110
|
+
assert json.loads(lanes_path.read_text()) == {"conv_A": "owner_lane"}
|
|
3111
|
+
assert agent_ws.last_session_id() == "owner_global"
|
|
3112
|
+
assert agent_ws.set_calls == []
|
|
3060
3113
|
|
|
3061
3114
|
|
|
3062
3115
|
async def test_qoder_real_event_sequence_converges_on_result(fake_claude, tmp_path):
|
|
@@ -72,6 +72,7 @@ def test_mcp_bridge_command_exists_even_though_hidden():
|
|
|
72
72
|
assert result.exit_code == 0
|
|
73
73
|
assert "--agent-id" in result.stdout
|
|
74
74
|
assert "--token-file" in result.stdout
|
|
75
|
+
assert "--tool-profile" in result.stdout
|
|
75
76
|
|
|
76
77
|
|
|
77
78
|
# ---------------------------------------------------------------------------
|
|
@@ -131,6 +131,13 @@ def test_build_bridge_args_default_profile_omits_flag() -> None:
|
|
|
131
131
|
]
|
|
132
132
|
|
|
133
133
|
|
|
134
|
+
def test_build_bridge_args_read_analyze_includes_tool_profile() -> None:
|
|
135
|
+
args = paths.build_mcp_bridge_args(
|
|
136
|
+
"agent_x", "/tok/t.txt", tool_profile="read_analyze"
|
|
137
|
+
)
|
|
138
|
+
assert args[-2:] == ["--tool-profile", "read_analyze"]
|
|
139
|
+
|
|
140
|
+
|
|
134
141
|
def test_build_bridge_args_none_falls_back_to_env(
|
|
135
142
|
monkeypatch: pytest.MonkeyPatch,
|
|
136
143
|
) -> None:
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.5.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/agent_upload_denylist.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/delegation_report.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/injection_state.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/qoder_credentials.py
RENAMED
|
File without changes
|
{cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/src/cloudagent_daemon/readonly_bash_guard.py
RENAMED
|
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
|
{cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/test_e2e_deliver_to_spawn.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/integration/test_spawn_mcp_bridge_e2e.py
RENAMED
|
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
|
{cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_claude_runner_codex_contract.py
RENAMED
|
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
|
{cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_mcp_bridge_agent_retrieval.py
RENAMED
|
File without changes
|
{cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_mcp_bridge_send_message.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_outbox_stuck_watchdog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_service_multi_profile.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.0 → cloudagent_daemon-0.5.1}/tests/unit/test_workspace_root_task113.py
RENAMED
|
File without changes
|
|
File without changes
|