Skip to content

Commit 3598a07

Browse files
committed
feat: Implement agent message received handling and update agent status logic
1 parent eef1b93 commit 3598a07

3 files changed

Lines changed: 21 additions & 10 deletions

File tree

src/db/crud.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,6 +1593,13 @@ async def agent_msg_wait(db: aiosqlite.Connection, agent_id: str, token: str) ->
15931593
return await _set_agent_activity(db, agent_id, "msg_wait", touch_heartbeat=True)
15941594

15951595

1596+
async def agent_msg_received(db: aiosqlite.Connection, agent_id: str) -> bool:
1597+
"""Mark that agent has received a message from msg_wait and is now processing.
1598+
This transitions the agent from Listening → Working on the frontend.
1599+
"""
1600+
return await _set_agent_activity(db, agent_id, "msg_received", touch_heartbeat=False)
1601+
1602+
15961603
async def agent_msg_post(db: aiosqlite.Connection, agent_id: str) -> bool:
15971604
return await _set_agent_activity(db, agent_id, "msg_post", touch_heartbeat=False)
15981605

src/static/js/shared-agent-status.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,31 @@
44
*
55
* States (priority order):
66
* Listening — SSE connected + last_activity is msg_wait (⏳)
7-
* Working — SSE connected + NOT msg_wait + activity within 60s (⚡)
8-
* Idle — SSE connected + NOT msg_wait + activity older than 60s (🔌)
7+
* Working — SSE connected + last_activity is msg_received/msg_post (⚡)
8+
* (no timeout — agent may be on a long task)
9+
* Idle — SSE connected + never entered message loop (🔌)
910
* (SSE open but agent stopped responding — may be processing
1011
* a long task or is a stale connection)
1112
* Offline — no SSE + heartbeat expired (⚫)
1213
*
1314
* For stdio agents (no SSE): fall back to heartbeat-based detection.
1415
* stdio agents will show Listening when is_online=true, Offline otherwise.
1516
*/
16-
const WORKING_TIMEOUT_S = 60;
17-
1817
function getAgentState(agent) {
1918
if (!agent) return "Offline";
2019

2120
if (agent.is_sse_connected) {
2221
if (agent.last_activity === "msg_wait") return "Listening";
23-
// Check how long ago the last activity was
24-
const lastActivityTime = agent.last_activity_time ? new Date(agent.last_activity_time) : null;
25-
if (lastActivityTime) {
26-
const elapsedS = (Date.now() - lastActivityTime.getTime()) / 1000;
27-
if (elapsedS > WORKING_TIMEOUT_S) return "Idle";
22+
// msg_received: msg_wait just delivered a message, agent is processing.
23+
// msg_post: agent just posted a reply, still in working cycle.
24+
// No timeout — agent may be doing a long task (editing code, reasoning, etc.)
25+
// SSE disconnect is the only reliable signal that work has truly stopped.
26+
if (agent.last_activity === "msg_received" || agent.last_activity === "msg_post") {
27+
return "Working";
2828
}
29-
return "Working";
29+
// Any other activity (registered, heartbeat, resume, etc.) with SSE open:
30+
// agent is connected but hasn't entered the message loop yet → Idle.
31+
return "Idle";
3032
}
3133

3234
// stdio or truly offline — rely on heartbeat

src/tools/dispatch.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,7 @@ async def _poll():
848848
# Exit wait state only when returning a message to caller.
849849
if agent_id:
850850
await crud.thread_wait_exit(db, thread_id, agent_id)
851+
await crud.agent_msg_received(db, agent_id)
851852
return filtered
852853
# Messages were present but not targeted at this waiter.
853854
# Move the local cursor forward to avoid polling the same
@@ -857,6 +858,7 @@ async def _poll():
857858
# No for_agent filter: any message wakes this waiter.
858859
if agent_id:
859860
await crud.thread_wait_exit(db, thread_id, agent_id)
861+
await crud.agent_msg_received(db, agent_id)
860862
return msgs
861863

862864
now = asyncio.get_event_loop().time()

0 commit comments

Comments
 (0)