cloudagent-daemon 0.5.5__tar.gz → 0.5.6__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.5 → cloudagent_daemon-0.5.6}/CHANGELOG.md +26 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/PKG-INFO +1 -1
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/pyproject.toml +1 -1
- cloudagent_daemon-0.5.6/src/cloudagent_daemon/__init__.py +1 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/claude_runner.py +54 -2
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_claude_runner.py +110 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/uv.lock +1 -1
- cloudagent_daemon-0.5.5/src/cloudagent_daemon/__init__.py +0 -1
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/.gitignore +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/LICENSE +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/README.md +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/scripts/release.sh +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/__main__.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/agent_download.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/agent_upload.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/agent_upload_denylist.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/auth_preflight.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/cli.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/config.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/context_fetch.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/delegation_report.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/doctor.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/frame_dispatch.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/http_outbound.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/injection_state.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/jsonl_logging.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/login.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/main_loop.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/mcp_bridge.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/metrics.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/metrics_server.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/outbound_queue.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/paths.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/protocol.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/qoder_credentials.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/readonly_bash_guard.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/runtime_config.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/runtime_detect.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/service.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/spawn_outbox.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/spawn_queue.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/sse_client.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/transport.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/unix_socket.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/workspace.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/ws_client.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/integration/__init__.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/integration/_fake_sse_server.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/integration/conftest.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/integration/test_e2e_deliver_to_spawn.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/integration/test_e2e_sse.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/integration/test_full_e2e.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/integration/test_mcp_bridge_round_trip.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/integration/test_one_shot_cli.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/integration/test_spawn_mcp_bridge_e2e.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/integration/test_sse_handshake.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/integration/test_sse_watchdog.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/integration/test_ws_handshake.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/integration/test_ws_reconnect.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/conftest.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_agent_download.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_agent_upload.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_auth_preflight.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_claude_runner_codex_contract.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_claude_runner_routing_contract.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_claude_runner_w3.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_cli_multi_profile.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_cli_smoke.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_config.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_context_fetch.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_delegation_report.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_doctor.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_frame_dispatch.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_http_outbound.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_injection_state.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_jsonl_logging.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_login.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_main_loop_assign.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_main_loop_preflight.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_mcp_bridge_agent_retrieval.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_mcp_bridge_send_message.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_metrics.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_metrics_server.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_multi_tool_wake.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_outbound_queue.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_outbox_stuck_watchdog.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_packaging_mcp_pin.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_paths.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_protocol.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_qoder_credentials.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_readonly_bash_guard.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_runtime_detect.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_service.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_service_multi_profile.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_spawn_outbox.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_spawn_queue.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_sse_active_watchdog.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_start_guard.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_status_stop.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_workspace.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_workspace_root_task113.py +0 -0
- {cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_ws_client_insecure.py +0 -0
|
@@ -4,6 +4,32 @@ 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.6] — 2026-08-30
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Every claude_code wake now passes `--autocompact 200k`. A lane is re-read in
|
|
11
|
+
full on every wake, so an unbounded one charges its whole history each time —
|
|
12
|
+
one agent's lane climbed to 1.07M cached tokens and was replaying all of it 96
|
|
13
|
+
times a day to add a few thousand tokens of new content. An explicit size is
|
|
14
|
+
used rather than `auto` because `auto` tracks the model's own window (1M on
|
|
15
|
+
opus), which is precisely the runaway being bounded. This prevents what
|
|
16
|
+
`_is_context_exhausted` (0.5.x, #114) only cures after a wake has already
|
|
17
|
+
failed and its session been discarded.
|
|
18
|
+
- A timer-fired wake (`message_id="remind:<id>:<n>"`) now runs in a throwaway
|
|
19
|
+
session: it does not resume a lane, does not record one, and its failure may
|
|
20
|
+
not clear one. A reminder tick is a stateless patrol — what it must decide
|
|
21
|
+
comes from the room's current state, which it reads live, not from what it
|
|
22
|
+
said on the previous tick.
|
|
23
|
+
|
|
24
|
+
### Notes
|
|
25
|
+
- Recording a patrol's session as the room's lane was the same bleed as #95,
|
|
26
|
+
arriving by another door: the next human to speak in that room inherited the
|
|
27
|
+
timer's accumulated history. Ordinary wakes are unaffected — a human message
|
|
28
|
+
in that same room still resumes the room's lane and still records its session.
|
|
29
|
+
- Hosts on 0.5.5 will see supervision-style agents start each tick fresh. That
|
|
30
|
+
is the change, not a regression: such an agent should be reading the room, not
|
|
31
|
+
replaying itself.
|
|
32
|
+
|
|
7
33
|
## [0.5.5] — 2026-08-27
|
|
8
34
|
|
|
9
35
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cloudagent-daemon
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.6
|
|
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.6"
|
|
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.6"
|
|
@@ -474,6 +474,16 @@ class ClaudeRunner:
|
|
|
474
474
|
"no rollout found for thread",
|
|
475
475
|
)
|
|
476
476
|
|
|
477
|
+
#: #114 followup — ``--autocompact`` window for claude_code wakes.
|
|
478
|
+
#:
|
|
479
|
+
#: An explicit size rather than ``auto``: ``auto`` tracks the model's own
|
|
480
|
+
#: window (1M on opus), which is the runaway this exists to bound — a lane
|
|
481
|
+
#: is replayed in full on every wake, so "compacts just before overflow"
|
|
482
|
+
#: still means every wake pays for a near-window-sized history. 200k keeps
|
|
483
|
+
#: days of one room's working context while cutting per-wake replay ~5x.
|
|
484
|
+
#: The CLI accepts ``auto`` or 100k–1M (``claude --help``).
|
|
485
|
+
AUTOCOMPACT_WINDOW: str = "200k"
|
|
486
|
+
|
|
477
487
|
#: #114 — 上下文窗口撑满。这是**永久性**前提失败,不是瞬时错误:退避几秒
|
|
478
488
|
#: 再把同一份历史送一次,必然再次溢出。codex 自己给出的处置就是 "Start a
|
|
479
489
|
#: new thread or clear earlier history",与 stale-session 的补救动作完全
|
|
@@ -692,6 +702,10 @@ The daemon discards stdout — only the mcp tool call reaches the channel.
|
|
|
692
702
|
context_exhausted = self._is_context_exhausted(attempt)
|
|
693
703
|
if (
|
|
694
704
|
self._run_mode_for(payload) != "read_analyze"
|
|
705
|
+
# A periodic wake resumed nothing, so a stale/exhausted session
|
|
706
|
+
# is not its to drop — clearing here would evict the lane the
|
|
707
|
+
# room's humans are still using.
|
|
708
|
+
and not self._is_periodic_wake(payload)
|
|
695
709
|
and attempt.exit_code != 0
|
|
696
710
|
and (
|
|
697
711
|
any(
|
|
@@ -781,6 +795,10 @@ The daemon discards stdout — only the mcp tool call reaches the channel.
|
|
|
781
795
|
# 防外人注入的上下文渗进 owner 后续 full 会话。仍上报 session_start
|
|
782
796
|
# 供实时展示,但不落 last-spawn(下次 full 唤醒仍 resume 干净的 owner 轨)。
|
|
783
797
|
is_read_analyze = self._run_mode_for(payload) == "read_analyze"
|
|
798
|
+
# #114 followup — a periodic wake ran in a throwaway session (it never
|
|
799
|
+
# resumed one). Recording it would hand the room's humans a lane whose
|
|
800
|
+
# history is a patrol tick, which is the bleed we just removed.
|
|
801
|
+
is_periodic = self._is_periodic_wake(payload)
|
|
784
802
|
# #95 — 所有引擎都按 conversation_id 分轨存会话;全局 last_session 仍写
|
|
785
803
|
# (last-spawn.json 可观测性 + 非平台唤醒兜底),但平台唤醒的 resume
|
|
786
804
|
# 只读 lane(见 _build_cmd),不会被别的频道的会话覆盖。
|
|
@@ -792,7 +810,7 @@ The daemon discards stdout — only the mcp tool call reaches the channel.
|
|
|
792
810
|
# 把 session_start 刷成噪音。必须在 _set_lane 之前读。
|
|
793
811
|
prev_lane_session = self._lane_session(ws, conv_for_lane)
|
|
794
812
|
if captured_session and captured_session != prev_lane_session:
|
|
795
|
-
if not is_read_analyze:
|
|
813
|
+
if not is_read_analyze and not is_periodic:
|
|
796
814
|
if conv_for_lane:
|
|
797
815
|
self._set_lane(ws, conv_for_lane, captured_session)
|
|
798
816
|
ws.set_last_session_id(captured_session)
|
|
@@ -1380,6 +1398,7 @@ The daemon discards stdout — only the mcp tool call reaches the channel.
|
|
|
1380
1398
|
model, reasoning_effort = self._model_and_effort_for(ws, payload)
|
|
1381
1399
|
runtime_env = self._runtime_env_for(ws, payload)
|
|
1382
1400
|
run_mode = self._run_mode_for(payload)
|
|
1401
|
+
periodic = self._is_periodic_wake(payload)
|
|
1383
1402
|
scratch_dir = (
|
|
1384
1403
|
self._readonly_scratch_dir(ws, payload)
|
|
1385
1404
|
if run_mode == "read_analyze"
|
|
@@ -1395,6 +1414,7 @@ The daemon discards stdout — only the mcp tool call reaches the channel.
|
|
|
1395
1414
|
reasoning_effort=reasoning_effort,
|
|
1396
1415
|
run_mode=run_mode,
|
|
1397
1416
|
scratch_dir=scratch_dir,
|
|
1417
|
+
periodic=periodic,
|
|
1398
1418
|
conversation_id=(
|
|
1399
1419
|
str(payload["conversation_id"])
|
|
1400
1420
|
if payload.get("conversation_id") else None
|
|
@@ -1918,6 +1938,27 @@ TASKS (频道任务板) — 频道里的分工协作走任务认领原语,纪
|
|
|
1918
1938
|
raw = payload.get("run_mode")
|
|
1919
1939
|
return "read_analyze" if raw == "read_analyze" else "full"
|
|
1920
1940
|
|
|
1941
|
+
@staticmethod
|
|
1942
|
+
def _is_periodic_wake(payload: dict) -> bool:
|
|
1943
|
+
"""True for a timer-fired wake — one occurrence of a reminder.
|
|
1944
|
+
|
|
1945
|
+
Backend stamps these with the synthetic message id
|
|
1946
|
+
``remind:<reminder_id>:<occurrence>``.
|
|
1947
|
+
|
|
1948
|
+
A periodic wake is a *stateless patrol*: what it has to decide comes
|
|
1949
|
+
from the room's CURRENT state, which it reads live (platform_read),
|
|
1950
|
+
not from what it said on the previous tick. So it neither resumes a
|
|
1951
|
+
lane nor writes one back — it runs in its own throwaway session.
|
|
1952
|
+
|
|
1953
|
+
Before this, a 15-minute supervision timer resumed its channel's lane
|
|
1954
|
+
96 times a day, replaying an ever-growing history to contribute a few
|
|
1955
|
+
thousand tokens of new content — the replay reached ~1M tokens per
|
|
1956
|
+
wake — and left that history behind for the humans in the room to
|
|
1957
|
+
inherit on their next message.
|
|
1958
|
+
"""
|
|
1959
|
+
mid = payload.get("message_id")
|
|
1960
|
+
return isinstance(mid, str) and mid.startswith("remind:")
|
|
1961
|
+
|
|
1921
1962
|
def _readonly_scratch_dir(
|
|
1922
1963
|
self, ws: AgentWorkspaceLike, payload: dict
|
|
1923
1964
|
) -> "Path":
|
|
@@ -2217,6 +2258,7 @@ TASKS (频道任务板) — 频道里的分工协作走任务认领原语,纪
|
|
|
2217
2258
|
run_mode: str = "full",
|
|
2218
2259
|
scratch_dir: "Path | None" = None,
|
|
2219
2260
|
conversation_id: str | None = None,
|
|
2261
|
+
periodic: bool = False,
|
|
2220
2262
|
) -> list[str]:
|
|
2221
2263
|
"""Build the ``claude`` argv list for one wake.
|
|
2222
2264
|
|
|
@@ -2243,7 +2285,7 @@ TASKS (频道任务板) — 频道里的分工协作走任务认领原语,纪
|
|
|
2243
2285
|
# 在哪个频道被唤醒都 --resume 同一条会话。
|
|
2244
2286
|
last_session = (
|
|
2245
2287
|
self._lane_session(ws, conversation_id)
|
|
2246
|
-
if (with_resume and not read_analyze) else None
|
|
2288
|
+
if (with_resume and not read_analyze and not periodic) else None
|
|
2247
2289
|
)
|
|
2248
2290
|
if runtime_engine == "codex":
|
|
2249
2291
|
binary = self.runtime_binaries.get("codex") or "codex"
|
|
@@ -2378,6 +2420,16 @@ TASKS (频道任务板) — 频道里的分工协作走任务认领原语,纪
|
|
|
2378
2420
|
cmd += ["--model", model]
|
|
2379
2421
|
if reasoning_effort:
|
|
2380
2422
|
cmd += ["--effort", reasoning_effort]
|
|
2423
|
+
# #114 followup — cap the context BEFORE it overflows.
|
|
2424
|
+
#
|
|
2425
|
+
# ``_is_context_exhausted`` is the cure and it is expensive: the wake
|
|
2426
|
+
# has already failed, and the remedy throws the whole session away.
|
|
2427
|
+
# ``--autocompact`` is the prevention — the CLI folds older turns into
|
|
2428
|
+
# a summary at the threshold, so a long-lived lane keeps its thread
|
|
2429
|
+
# instead of being discarded whole. It also bounds what every wake
|
|
2430
|
+
# REPLAYS, which is the larger bill: a lane is re-read in full on each
|
|
2431
|
+
# wake, so an unbounded one charges its entire history every time.
|
|
2432
|
+
cmd += ["--autocompact", self.AUTOCOMPACT_WINDOW]
|
|
2381
2433
|
if last_session:
|
|
2382
2434
|
cmd += ["--resume", last_session]
|
|
2383
2435
|
# 1i Bug F — inject operational contract via --append-system-prompt.
|
|
@@ -3846,3 +3846,113 @@ async def test_no_conversation_id_means_no_notice_but_never_a_crash_of_the_wake(
|
|
|
3846
3846
|
if inv.get("tool_name") == "platform_send"
|
|
3847
3847
|
]
|
|
3848
3848
|
assert not sends
|
|
3849
|
+
|
|
3850
|
+
|
|
3851
|
+
# ---------------------------------------------------------------------------
|
|
3852
|
+
# #114 followup — bound what a wake replays (A: --autocompact), and keep
|
|
3853
|
+
# timers off the room's thread (B: a periodic wake is a throwaway session).
|
|
3854
|
+
#
|
|
3855
|
+
# Both target the same bill from opposite ends. A lane is re-read IN FULL on
|
|
3856
|
+
# every wake, so an unbounded one charges its whole history each time; a
|
|
3857
|
+
# 15-minute timer made that 96 times a day while contributing a few thousand
|
|
3858
|
+
# tokens of new content, and left the accumulated patrol history behind for
|
|
3859
|
+
# whoever spoke in the room next.
|
|
3860
|
+
# ---------------------------------------------------------------------------
|
|
3861
|
+
|
|
3862
|
+
|
|
3863
|
+
def _reminder_payload(reminder_id: str = "rem_1", occurrence: int = 0, **extra) -> dict:
|
|
3864
|
+
"""A timer wake as the backend stamps it: ``remind:<reminder_id>:<n>``."""
|
|
3865
|
+
return {
|
|
3866
|
+
**_make_payload(message_id=f"remind:{reminder_id}:{occurrence}"),
|
|
3867
|
+
**extra,
|
|
3868
|
+
}
|
|
3869
|
+
|
|
3870
|
+
|
|
3871
|
+
async def test_every_claude_wake_caps_its_context_window(
|
|
3872
|
+
stateful_fake_claude, tmp_path
|
|
3873
|
+
):
|
|
3874
|
+
"""A — the cap is on every wake, not only the ones that already grew."""
|
|
3875
|
+
mcp_cfg = tmp_path / "mcp.json"; mcp_cfg.write_text("{}")
|
|
3876
|
+
ws = FakeWorkspace(FakeAgentWorkspace(mcp_path=mcp_cfg, last_session=None))
|
|
3877
|
+
runner = _build_runner(
|
|
3878
|
+
stateful_fake_claude([_claude_ok("s1")]), ws, FakeOutbound()
|
|
3879
|
+
)
|
|
3880
|
+
|
|
3881
|
+
await runner.run_one("agent-A", _make_payload())
|
|
3882
|
+
|
|
3883
|
+
argv = _read_argv_n(tmp_path, 0)
|
|
3884
|
+
assert argv[argv.index("--autocompact") + 1] == ClaudeRunner.AUTOCOMPACT_WINDOW
|
|
3885
|
+
|
|
3886
|
+
|
|
3887
|
+
async def test_periodic_wake_does_not_resume_the_rooms_lane(
|
|
3888
|
+
stateful_fake_claude, tmp_path
|
|
3889
|
+
):
|
|
3890
|
+
"""B — a patrol reads the room's CURRENT state; it must not replay its past."""
|
|
3891
|
+
mcp_cfg = tmp_path / "mcp.json"; mcp_cfg.write_text("{}")
|
|
3892
|
+
(tmp_path / ".meta").mkdir(exist_ok=True)
|
|
3893
|
+
(tmp_path / ".meta" / "sessions.json").write_text(
|
|
3894
|
+
json.dumps({"conv_room": "human_thread"})
|
|
3895
|
+
)
|
|
3896
|
+
ws = FakeWorkspace(FakeAgentWorkspace(mcp_path=mcp_cfg, last_session=None))
|
|
3897
|
+
runner = _build_runner(
|
|
3898
|
+
stateful_fake_claude([_claude_ok("patrol_thread")]), ws, FakeOutbound()
|
|
3899
|
+
)
|
|
3900
|
+
|
|
3901
|
+
await runner.run_one("agent-A", _reminder_payload(conversation_id="conv_room"))
|
|
3902
|
+
|
|
3903
|
+
assert "--resume" not in _read_argv_n(tmp_path, 0)
|
|
3904
|
+
|
|
3905
|
+
|
|
3906
|
+
async def test_periodic_wake_leaves_the_rooms_lane_untouched(
|
|
3907
|
+
stateful_fake_claude, tmp_path
|
|
3908
|
+
):
|
|
3909
|
+
"""B — and it writes nothing back: neither the room's lane nor the global.
|
|
3910
|
+
|
|
3911
|
+
This is the regression itself. A timer that records its throwaway session
|
|
3912
|
+
hands its patrol history to the next human who speaks in that room.
|
|
3913
|
+
"""
|
|
3914
|
+
mcp_cfg = tmp_path / "mcp.json"; mcp_cfg.write_text("{}")
|
|
3915
|
+
(tmp_path / ".meta").mkdir(exist_ok=True)
|
|
3916
|
+
(tmp_path / ".meta" / "sessions.json").write_text(
|
|
3917
|
+
json.dumps({"conv_room": "human_thread"})
|
|
3918
|
+
)
|
|
3919
|
+
agent_ws = FakeAgentWorkspace(mcp_path=mcp_cfg, last_session=None)
|
|
3920
|
+
runner = _build_runner(
|
|
3921
|
+
stateful_fake_claude([_claude_ok("patrol_thread")]),
|
|
3922
|
+
FakeWorkspace(agent_ws),
|
|
3923
|
+
FakeOutbound(),
|
|
3924
|
+
)
|
|
3925
|
+
|
|
3926
|
+
await runner.run_one("agent-A", _reminder_payload(conversation_id="conv_room"))
|
|
3927
|
+
|
|
3928
|
+
lanes = json.loads((tmp_path / ".meta" / "sessions.json").read_text())
|
|
3929
|
+
assert lanes == {"conv_room": "human_thread"} # patrol_thread did NOT land
|
|
3930
|
+
assert agent_ws.set_calls == [] # nor did the global lane move
|
|
3931
|
+
|
|
3932
|
+
|
|
3933
|
+
async def test_ordinary_wake_in_that_room_still_resumes_and_records(
|
|
3934
|
+
stateful_fake_claude, tmp_path
|
|
3935
|
+
):
|
|
3936
|
+
"""B control — only TIMER wakes are throwaway. A human message in the same
|
|
3937
|
+
room still resumes that room's lane and still records its session."""
|
|
3938
|
+
mcp_cfg = tmp_path / "mcp.json"; mcp_cfg.write_text("{}")
|
|
3939
|
+
(tmp_path / ".meta").mkdir(exist_ok=True)
|
|
3940
|
+
(tmp_path / ".meta" / "sessions.json").write_text(
|
|
3941
|
+
json.dumps({"conv_room": "human_thread"})
|
|
3942
|
+
)
|
|
3943
|
+
agent_ws = FakeAgentWorkspace(mcp_path=mcp_cfg, last_session=None)
|
|
3944
|
+
runner = _build_runner(
|
|
3945
|
+
stateful_fake_claude([_claude_ok("human_thread_2")]),
|
|
3946
|
+
FakeWorkspace(agent_ws),
|
|
3947
|
+
FakeOutbound(),
|
|
3948
|
+
)
|
|
3949
|
+
|
|
3950
|
+
await runner.run_one(
|
|
3951
|
+
"agent-A", {**_make_payload(), "conversation_id": "conv_room"}
|
|
3952
|
+
)
|
|
3953
|
+
|
|
3954
|
+
argv = _read_argv_n(tmp_path, 0)
|
|
3955
|
+
assert argv[argv.index("--resume") + 1] == "human_thread"
|
|
3956
|
+
lanes = json.loads((tmp_path / ".meta" / "sessions.json").read_text())
|
|
3957
|
+
assert lanes == {"conv_room": "human_thread_2"}
|
|
3958
|
+
assert agent_ws.set_calls == ["human_thread_2"]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.5.5"
|
|
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.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/agent_upload_denylist.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/delegation_report.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/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
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/src/cloudagent_daemon/qoder_credentials.py
RENAMED
|
File without changes
|
{cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/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.5 → cloudagent_daemon-0.5.6}/tests/integration/test_e2e_deliver_to_spawn.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/integration/test_mcp_bridge_round_trip.py
RENAMED
|
File without changes
|
|
File without changes
|
{cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/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.5 → cloudagent_daemon-0.5.6}/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
|
|
File without changes
|
{cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/tests/unit/test_mcp_bridge_agent_retrieval.py
RENAMED
|
File without changes
|
{cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/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.5 → cloudagent_daemon-0.5.6}/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
|
|
File without changes
|
{cloudagent_daemon-0.5.5 → cloudagent_daemon-0.5.6}/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.5 → cloudagent_daemon-0.5.6}/tests/unit/test_workspace_root_task113.py
RENAMED
|
File without changes
|
|
File without changes
|