agent-manager-cli 0.1.8__tar.gz → 0.1.9__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.8 → agent_manager_cli-0.1.9}/PKG-INFO +1 -1
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/am/cli.py +60 -3
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/pyproject.toml +1 -1
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/.gitignore +0 -0
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/README.md +0 -0
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/am/__init__.py +0 -0
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/am/__main__.py +0 -0
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/am/scanners/__init__.py +0 -0
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/am/scanners/_util.py +0 -0
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/am/scanners/claude.py +0 -0
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/am/scanners/codex.py +0 -0
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/am/scanners/gemini.py +0 -0
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/am/schemas/__init__.py +0 -0
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/am/schemas/session.py +0 -0
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/tests/__init__.py +0 -0
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/tests/test_cli.py +0 -0
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/tests/test_event_mapping.py +0 -0
- {agent_manager_cli-0.1.8 → agent_manager_cli-0.1.9}/tests/test_scanners.py +0 -0
|
@@ -656,8 +656,39 @@ async def run_daemon_bidirectional(ws_url: str, ws_token: str, command: list[str
|
|
|
656
656
|
|
|
657
657
|
stdout_task = asyncio.create_task(_read_stdout())
|
|
658
658
|
ws_task = asyncio.create_task(_read_ws())
|
|
659
|
-
|
|
660
|
-
|
|
659
|
+
|
|
660
|
+
# Run both in parallel. Normally stdout_task finishes first
|
|
661
|
+
# (Claude exits naturally at end of task). If ws_task finishes
|
|
662
|
+
# first it means the backend connection died — in that case we
|
|
663
|
+
# have no way to recover the session, so we terminate Claude
|
|
664
|
+
# so the daemon subprocess exits cleanly. The node agent will
|
|
665
|
+
# detect the dead subprocess and spawn a fresh one on the next
|
|
666
|
+
# attach command (with --continue --resume so history is kept).
|
|
667
|
+
done, _pending = await asyncio.wait(
|
|
668
|
+
[stdout_task, ws_task],
|
|
669
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
if ws_task in done and proc.returncode is None:
|
|
673
|
+
_log(
|
|
674
|
+
"daemon",
|
|
675
|
+
"WS closed while Claude is still running — terminating CLI "
|
|
676
|
+
"(node agent will respawn on next attach)",
|
|
677
|
+
)
|
|
678
|
+
try:
|
|
679
|
+
proc.terminate()
|
|
680
|
+
except ProcessLookupError:
|
|
681
|
+
pass
|
|
682
|
+
|
|
683
|
+
# Let remaining tasks clean up (stdout_task should exit once
|
|
684
|
+
# claude's stdout closes after terminate).
|
|
685
|
+
for task in (stdout_task, ws_task):
|
|
686
|
+
if not task.done():
|
|
687
|
+
task.cancel()
|
|
688
|
+
try:
|
|
689
|
+
await task
|
|
690
|
+
except (asyncio.CancelledError, Exception):
|
|
691
|
+
pass
|
|
661
692
|
|
|
662
693
|
proc.wait()
|
|
663
694
|
_log("daemon", f"Process exited with code {proc.returncode}")
|
|
@@ -888,8 +919,34 @@ async def _run_node_agent(
|
|
|
888
919
|
async def _heartbeat():
|
|
889
920
|
while True:
|
|
890
921
|
await asyncio.sleep(25)
|
|
922
|
+
# Reap any daemon subprocesses that have exited so we
|
|
923
|
+
# don't report them as alive.
|
|
924
|
+
dead = [
|
|
925
|
+
aid for aid, p in daemon_procs.items()
|
|
926
|
+
if p.returncode is not None
|
|
927
|
+
]
|
|
928
|
+
for aid in dead:
|
|
929
|
+
daemon_procs.pop(aid, None)
|
|
930
|
+
|
|
931
|
+
# Report live daemons to the backend so it can refresh
|
|
932
|
+
# `daemon:live:{id}` heartbeats. This is the source of
|
|
933
|
+
# truth for the UI's `is_daemon_alive` check — without
|
|
934
|
+
# it the backend has no way to know when a daemon
|
|
935
|
+
# subprocess is alive but its own ws to the backend is
|
|
936
|
+
# dead (orphaned after backend restart, etc.).
|
|
937
|
+
alive_ids = [
|
|
938
|
+
aid for aid, p in daemon_procs.items()
|
|
939
|
+
if p.returncode is None
|
|
940
|
+
]
|
|
891
941
|
try:
|
|
892
|
-
await ws.send(
|
|
942
|
+
await ws.send(
|
|
943
|
+
json.dumps(
|
|
944
|
+
{
|
|
945
|
+
"type": "heartbeat",
|
|
946
|
+
"daemons": alive_ids,
|
|
947
|
+
}
|
|
948
|
+
)
|
|
949
|
+
)
|
|
893
950
|
except websockets.exceptions.ConnectionClosed as e:
|
|
894
951
|
_log("node", f"heartbeat: connection closed ({e.code} {e.reason or '-'})")
|
|
895
952
|
break
|
|
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
|