agent-manager-cli 0.1.3__tar.gz → 0.1.4__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.
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/PKG-INFO +1 -1
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/am/cli.py +66 -20
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/pyproject.toml +1 -1
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/.gitignore +0 -0
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/README.md +0 -0
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/am/__init__.py +0 -0
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/am/__main__.py +0 -0
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/am/scanners/__init__.py +0 -0
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/am/scanners/_util.py +0 -0
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/am/scanners/claude.py +0 -0
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/am/scanners/codex.py +0 -0
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/am/scanners/gemini.py +0 -0
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/am/schemas/__init__.py +0 -0
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/am/schemas/session.py +0 -0
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/tests/__init__.py +0 -0
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/tests/test_cli.py +0 -0
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/tests/test_event_mapping.py +0 -0
- {agent_manager_cli-0.1.3 → agent_manager_cli-0.1.4}/tests/test_scanners.py +0 -0
|
@@ -79,7 +79,8 @@ def map_stream_event(line: str) -> list[dict]:
|
|
|
79
79
|
return []
|
|
80
80
|
|
|
81
81
|
if msg_type == "assistant":
|
|
82
|
-
|
|
82
|
+
message = msg.get("message", {})
|
|
83
|
+
content_blocks = message.get("content", [])
|
|
83
84
|
events = []
|
|
84
85
|
for block in content_blocks:
|
|
85
86
|
block_type = block.get("type")
|
|
@@ -108,6 +109,20 @@ def map_stream_event(line: str) -> list[dict]:
|
|
|
108
109
|
},
|
|
109
110
|
}
|
|
110
111
|
)
|
|
112
|
+
# Emit token usage delta if present in this assistant message.
|
|
113
|
+
usage = message.get("usage") or {}
|
|
114
|
+
if usage:
|
|
115
|
+
events.append(
|
|
116
|
+
{
|
|
117
|
+
"event_type": "usage",
|
|
118
|
+
"data": {
|
|
119
|
+
"input_tokens": usage.get("input_tokens", 0),
|
|
120
|
+
"output_tokens": usage.get("output_tokens", 0),
|
|
121
|
+
"cache_creation_input_tokens": usage.get("cache_creation_input_tokens", 0),
|
|
122
|
+
"cache_read_input_tokens": usage.get("cache_read_input_tokens", 0),
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
)
|
|
111
126
|
return events
|
|
112
127
|
|
|
113
128
|
if msg_type == "user":
|
|
@@ -372,14 +387,16 @@ async def run_daemon_bidirectional(ws_url: str, ws_token: str, command: list[str
|
|
|
372
387
|
|
|
373
388
|
try:
|
|
374
389
|
raw = json.loads(line)
|
|
375
|
-
|
|
390
|
+
raw_type = raw.get("type", "?")
|
|
391
|
+
_log("raw", f"#{line_num} type={raw_type}")
|
|
392
|
+
if raw_type == "system" and raw.get("subtype") == "init":
|
|
376
393
|
session_id = raw.get("session_id")
|
|
377
|
-
elif
|
|
394
|
+
elif raw_type == "result":
|
|
378
395
|
sid = raw.get("session_id")
|
|
379
396
|
if sid:
|
|
380
397
|
session_id = sid
|
|
381
398
|
except (json.JSONDecodeError, KeyError):
|
|
382
|
-
|
|
399
|
+
_log("raw", f"#{line_num} (not json) {line[:80]}")
|
|
383
400
|
|
|
384
401
|
events = map_stream_event(line)
|
|
385
402
|
if not events:
|
|
@@ -630,7 +647,8 @@ async def _run_node_agent(host: str, secure: bool, access_token: str) -> None:
|
|
|
630
647
|
)
|
|
631
648
|
)
|
|
632
649
|
_log("node", f"Sent {len(sessions)} sessions")
|
|
633
|
-
except websockets.exceptions.ConnectionClosed:
|
|
650
|
+
except websockets.exceptions.ConnectionClosed as e:
|
|
651
|
+
_log("node", f"scan: connection closed ({e.code} {e.reason or '-'})")
|
|
634
652
|
break
|
|
635
653
|
except Exception as e:
|
|
636
654
|
_log("node", f"Scan error: {e}")
|
|
@@ -641,7 +659,8 @@ async def _run_node_agent(host: str, secure: bool, access_token: str) -> None:
|
|
|
641
659
|
await asyncio.sleep(25)
|
|
642
660
|
try:
|
|
643
661
|
await ws.send(json.dumps({"type": "heartbeat"}))
|
|
644
|
-
except websockets.exceptions.ConnectionClosed:
|
|
662
|
+
except websockets.exceptions.ConnectionClosed as e:
|
|
663
|
+
_log("node", f"heartbeat: connection closed ({e.code} {e.reason or '-'})")
|
|
645
664
|
break
|
|
646
665
|
|
|
647
666
|
async def _listen_commands():
|
|
@@ -654,8 +673,8 @@ async def _run_node_agent(host: str, secure: bool, access_token: str) -> None:
|
|
|
654
673
|
|
|
655
674
|
if cmd.get("type") == "attach":
|
|
656
675
|
await _handle_attach(cmd, host, secure, daemon_procs)
|
|
657
|
-
except websockets.exceptions.ConnectionClosed:
|
|
658
|
-
|
|
676
|
+
except websockets.exceptions.ConnectionClosed as e:
|
|
677
|
+
_log("node", f"listen: connection closed ({e.code} {e.reason or '-'})")
|
|
659
678
|
|
|
660
679
|
scan_task = asyncio.create_task(_scan_and_send())
|
|
661
680
|
hb_task = asyncio.create_task(_heartbeat())
|
|
@@ -763,18 +782,45 @@ def cmd_connect(args: argparse.Namespace) -> None:
|
|
|
763
782
|
save_config(config)
|
|
764
783
|
_log("connect", "Token refreshed.")
|
|
765
784
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
785
|
+
# Reconnect loop with exponential backoff. On clean disconnect (network
|
|
786
|
+
# blip, backend restart, proxy timeout) we retry automatically. Auth
|
|
787
|
+
# errors (401/403) bail out immediately — the user must re-login.
|
|
788
|
+
import time as _time
|
|
789
|
+
|
|
790
|
+
backoff = 1
|
|
791
|
+
while True:
|
|
792
|
+
try:
|
|
793
|
+
asyncio.run(_run_node_agent(host, secure, access_token))
|
|
794
|
+
# _run_node_agent returned normally → ws was closed by server or
|
|
795
|
+
# one of the tasks finished. Reconnect after a short delay.
|
|
796
|
+
_log("connect", f"Disconnected. Reconnecting in {backoff}s...")
|
|
797
|
+
except KeyboardInterrupt:
|
|
798
|
+
print("\n[connect] Stopped.", file=sys.stderr)
|
|
799
|
+
return
|
|
800
|
+
except websockets.exceptions.InvalidStatusCode as e:
|
|
801
|
+
if e.status_code in (401, 403):
|
|
802
|
+
_log("connect", f"Auth failed ({e.status_code}). Run `am login` again.")
|
|
803
|
+
sys.exit(1)
|
|
804
|
+
_log("connect", f"WebSocket error: {e}, retrying in {backoff}s...")
|
|
805
|
+
except OSError as e:
|
|
806
|
+
# DNS / connect refused / network unreachable — keep trying.
|
|
807
|
+
_log("connect", f"Network error: {e}, retrying in {backoff}s...")
|
|
808
|
+
except Exception as e:
|
|
809
|
+
_log("connect", f"Error: {e} ({type(e).__name__}), retrying in {backoff}s...")
|
|
810
|
+
|
|
811
|
+
_time.sleep(backoff)
|
|
812
|
+
backoff = min(backoff * 2, 30) # cap at 30 seconds
|
|
813
|
+
|
|
814
|
+
# After a reconnect attempt, try to refresh the token again in case
|
|
815
|
+
# it expired while we were disconnected for a long period.
|
|
816
|
+
if refresh_token:
|
|
817
|
+
tokens = _refresh_token(host, secure, refresh_token)
|
|
818
|
+
if tokens:
|
|
819
|
+
access_token = tokens["access_token"]
|
|
820
|
+
refresh_token = tokens["refresh_token"]
|
|
821
|
+
config["access_token"] = access_token
|
|
822
|
+
config["refresh_token"] = refresh_token
|
|
823
|
+
save_config(config)
|
|
778
824
|
|
|
779
825
|
|
|
780
826
|
# ---------------------------------------------------------------------------
|
|
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
|