From 4c3a5b8049c2721bb5694c54d98eaed3ec4674b3 Mon Sep 17 00:00:00 2001 From: Hoang Nguyen Date: Wed, 28 Oct 2026 22:43:56 +0100 Subject: [PATCH] fix(agent): keep waiting agents from decaying to idle Nine harnesses (codex, gemini, copilot, grok, pi, kiro, opencode, antigravity, devin) checked the 5-minute idle threshold before the waiting condition, so an agent blocked on input for more than five minutes was reported idle and sorted below running agents. Check waiting first in both the TS adapters and the Rust devkit-harness port; idle now only applies to agents that are not awaiting input. Fixtures and tests updated to match. --- fixtures/harness/antigravity_cli/matched.json | 2 +- fixtures/harness/codex/statuses.json | 2 +- fixtures/harness/grok_cli/fallback.json | 2 +- .../harnesses/devin/DevinAgentMapper.test.ts | 15 +++++++++++++- .../harnesses/gemini/GeminiCliAdapter.test.ts | 14 ++++++++++++- .../opencode/OpenCodeAgentMapper.test.ts | 20 ++++++++++++++++++- .../antigravity/AntigravitySessionParser.ts | 8 ++++---- .../src/harnesses/codex/CodexSessionParser.ts | 8 ++++---- .../harnesses/copilot/CopilotSessionParser.ts | 8 ++++---- .../src/harnesses/devin/DevinAgentMapper.ts | 4 ++-- .../harnesses/gemini/GeminiSessionParser.ts | 8 ++++---- .../src/harnesses/grok/GrokSessionParser.ts | 8 ++++---- .../src/harnesses/kiro/KiroSessionParser.ts | 4 ++-- .../harnesses/opencode/OpenCodeAgentMapper.ts | 8 +++----- .../src/harnesses/pi/PiSessionParser.ts | 2 +- .../devkit-harness/src/antigravity/parser.rs | 10 +++++----- .../crates/devkit-harness/src/codex/parser.rs | 19 ++++++++++++++---- .../devkit-harness/src/copilot/parser.rs | 10 +++++----- rust/crates/devkit-harness/src/devin/mod.rs | 7 +++---- .../devkit-harness/src/gemini/parser.rs | 6 ++---- rust/crates/devkit-harness/src/grok/parser.rs | 8 ++++---- rust/crates/devkit-harness/src/kiro/parser.rs | 10 +++++----- .../crates/devkit-harness/src/opencode/mod.rs | 17 ++++++++-------- rust/crates/devkit-harness/src/pi/parser.rs | 6 ++---- 24 files changed, 126 insertions(+), 80 deletions(-) diff --git a/fixtures/harness/antigravity_cli/matched.json b/fixtures/harness/antigravity_cli/matched.json index 80e68c9a3..754d854b0 100644 --- a/fixtures/harness/antigravity_cli/matched.json +++ b/fixtures/harness/antigravity_cli/matched.json @@ -111,7 +111,7 @@ { "name": "idle-3003", "type": "antigravity_cli", - "status": "idle", + "status": "waiting", "summary": "Antigravity CLI session active", "pid": 3003, "projectPath": "/proj/idle", diff --git a/fixtures/harness/codex/statuses.json b/fixtures/harness/codex/statuses.json index fffde99f9..170659610 100644 --- a/fixtures/harness/codex/statuses.json +++ b/fixtures/harness/codex/statuses.json @@ -100,7 +100,7 @@ { "name": "st-idle-4005", "type": "codex", - "status": "idle", + "status": "waiting", "summary": "old reply", "pid": 4005, "projectPath": "/proj/st-idle", diff --git a/fixtures/harness/grok_cli/fallback.json b/fixtures/harness/grok_cli/fallback.json index d3f2d7f17..dad02020e 100644 --- a/fixtures/harness/grok_cli/fallback.json +++ b/fixtures/harness/grok_cli/fallback.json @@ -80,7 +80,7 @@ { "name": "idle-81004", "type": "grok_cli", - "status": "idle", + "status": "waiting", "summary": "old prompt", "pid": 81004, "projectPath": "/proj/idle", diff --git a/packages/agent-manager/src/__tests__/harnesses/devin/DevinAgentMapper.test.ts b/packages/agent-manager/src/__tests__/harnesses/devin/DevinAgentMapper.test.ts index f76e16f9d..c67c65643 100644 --- a/packages/agent-manager/src/__tests__/harnesses/devin/DevinAgentMapper.test.ts +++ b/packages/agent-manager/src/__tests__/harnesses/devin/DevinAgentMapper.test.ts @@ -56,7 +56,7 @@ describe("DevinAgentMapper", () => { const agent = mapper.mapSessionToAgent( session(), { - lastRole: "assistant", + lastRole: "user", lastTimeUpdated: Date.now() - 10 * 60 * 1000, summary: "", }, @@ -65,6 +65,19 @@ describe("DevinAgentMapper", () => { expect(agent.status).toBe(AgentStatus.IDLE); }); + it("keeps stale sessions awaiting input waiting", () => { + const agent = mapper.mapSessionToAgent( + session(), + { + lastRole: "assistant", + lastTimeUpdated: Date.now() - 10 * 60 * 1000, + summary: "", + }, + proc(), + ); + expect(agent.status).toBe(AgentStatus.WAITING); + }); + it("falls back to session title then directory for summary and project path", () => { const agent = mapper.mapSessionToAgent( session({ title: "Hello Greeting Session" }), diff --git a/packages/agent-manager/src/__tests__/harnesses/gemini/GeminiCliAdapter.test.ts b/packages/agent-manager/src/__tests__/harnesses/gemini/GeminiCliAdapter.test.ts index 994dd1c7f..5a598c73b 100644 --- a/packages/agent-manager/src/__tests__/harnesses/gemini/GeminiCliAdapter.test.ts +++ b/packages/agent-manager/src/__tests__/harnesses/gemini/GeminiCliAdapter.test.ts @@ -823,10 +823,22 @@ describe("GeminiCliAdapter", () => { summary: "", sessionStart: new Date(), lastActive: new Date(Date.now() - 10 * 60 * 1000), - lastMessageType: "gemini", + lastMessageType: "user", }; expect(parser.determineStatus(session)).toBe(AgentStatus.IDLE); }); + + it('should stay "waiting" past the idle threshold when awaiting input', () => { + const session = { + sessionId: "s", + projectPath: "", + summary: "", + sessionStart: new Date(), + lastActive: new Date(Date.now() - 10 * 60 * 1000), + lastMessageType: "gemini", + }; + expect(parser.determineStatus(session)).toBe(AgentStatus.WAITING); + }); }); describe("parseSession", () => { diff --git a/packages/agent-manager/src/__tests__/harnesses/opencode/OpenCodeAgentMapper.test.ts b/packages/agent-manager/src/__tests__/harnesses/opencode/OpenCodeAgentMapper.test.ts index 9f250d398..729059ca5 100644 --- a/packages/agent-manager/src/__tests__/harnesses/opencode/OpenCodeAgentMapper.test.ts +++ b/packages/agent-manager/src/__tests__/harnesses/opencode/OpenCodeAgentMapper.test.ts @@ -58,7 +58,7 @@ describe("OpenCodeAgentMapper", () => { { lastRole: "assistant", lastTimeUpdated: staleTime, - lastAssistantCompleted: true, + lastAssistantCompleted: false, lastAssistantErrored: false, summary: "", }, @@ -68,6 +68,24 @@ describe("OpenCodeAgentMapper", () => { expect(agent.status).toBe(AgentStatus.IDLE); }); + it("keeps stale sessions awaiting input waiting", () => { + const staleTime = Date.now() - 10 * 60 * 1000; + + const agent = mapper.mapSessionToAgent( + { sessionId: "sess-4", directory: "/old", timeCreated: staleTime }, + { + lastRole: "assistant", + lastTimeUpdated: staleTime, + lastAssistantCompleted: true, + lastAssistantErrored: false, + summary: "", + }, + { pid: 126, command: "opencode", cwd: "/old" }, + ); + + expect(agent.status).toBe(AgentStatus.WAITING); + }); + it("maps unmatched processes as running process-only agents", () => { const agent = mapper.mapProcessOnlyAgent({ pid: 500, command: "opencode", cwd: "/repo" }); diff --git a/packages/agent-manager/src/harnesses/antigravity/AntigravitySessionParser.ts b/packages/agent-manager/src/harnesses/antigravity/AntigravitySessionParser.ts index 2369c540f..d4b1651f8 100644 --- a/packages/agent-manager/src/harnesses/antigravity/AntigravitySessionParser.ts +++ b/packages/agent-manager/src/harnesses/antigravity/AntigravitySessionParser.ts @@ -130,17 +130,17 @@ export class AntigravitySessionParser { } /** + * - last turn is an assistant reply → WAITING (awaiting input) * - past the idle threshold → IDLE - * - last turn is an assistant reply → WAITING (awaiting user) * - otherwise (last turn was a user message, or unknown) → RUNNING */ determineStatus(session: AntigravitySession): AgentStatus { - if (isIdle(session.lastActive)) { - return AgentStatus.IDLE; - } if (session.lastRole === "assistant") { return AgentStatus.WAITING; } + if (isIdle(session.lastActive)) { + return AgentStatus.IDLE; + } return AgentStatus.RUNNING; } diff --git a/packages/agent-manager/src/harnesses/codex/CodexSessionParser.ts b/packages/agent-manager/src/harnesses/codex/CodexSessionParser.ts index 21e924f0d..b3b4b86b4 100644 --- a/packages/agent-manager/src/harnesses/codex/CodexSessionParser.ts +++ b/packages/agent-manager/src/harnesses/codex/CodexSessionParser.ts @@ -188,10 +188,6 @@ export class CodexSessionParser { } determineStatus(session: CodexSession): AgentStatus { - if (isIdle(session.lastActive)) { - return AgentStatus.IDLE; - } - if ( session.lastPayloadType === "agent_message" || session.lastPayloadType === "task_complete" || @@ -200,6 +196,10 @@ export class CodexSessionParser { return AgentStatus.WAITING; } + if (isIdle(session.lastActive)) { + return AgentStatus.IDLE; + } + return AgentStatus.RUNNING; } diff --git a/packages/agent-manager/src/harnesses/copilot/CopilotSessionParser.ts b/packages/agent-manager/src/harnesses/copilot/CopilotSessionParser.ts index 02bd5f400..fe1d933c7 100644 --- a/packages/agent-manager/src/harnesses/copilot/CopilotSessionParser.ts +++ b/packages/agent-manager/src/harnesses/copilot/CopilotSessionParser.ts @@ -276,14 +276,14 @@ export class CopilotSessionParser { } determineStatus(session: CopilotSession): AgentStatus { - if (isIdle(session.lastActive)) { - return AgentStatus.IDLE; - } - if (session.lastEventType !== undefined && WAITING_EVENTS.has(session.lastEventType)) { return AgentStatus.WAITING; } + if (isIdle(session.lastActive)) { + return AgentStatus.IDLE; + } + return AgentStatus.RUNNING; } diff --git a/packages/agent-manager/src/harnesses/devin/DevinAgentMapper.ts b/packages/agent-manager/src/harnesses/devin/DevinAgentMapper.ts index c11d12554..fed721ed6 100644 --- a/packages/agent-manager/src/harnesses/devin/DevinAgentMapper.ts +++ b/packages/agent-manager/src/harnesses/devin/DevinAgentMapper.ts @@ -33,7 +33,7 @@ export class DevinAgentMapper { } private determineStatus(stats: DevinSessionStats, lastActive: Date): AgentStatus { - if (isIdle(lastActive)) return AgentStatus.IDLE; - return stats.lastRole === "assistant" ? AgentStatus.WAITING : AgentStatus.RUNNING; + if (stats.lastRole === "assistant") return AgentStatus.WAITING; + return isIdle(lastActive) ? AgentStatus.IDLE : AgentStatus.RUNNING; } } diff --git a/packages/agent-manager/src/harnesses/gemini/GeminiSessionParser.ts b/packages/agent-manager/src/harnesses/gemini/GeminiSessionParser.ts index c86da05f2..62350b2cc 100644 --- a/packages/agent-manager/src/harnesses/gemini/GeminiSessionParser.ts +++ b/packages/agent-manager/src/harnesses/gemini/GeminiSessionParser.ts @@ -141,14 +141,14 @@ export class GeminiSessionParser { } determineStatus(session: GeminiSession): AgentStatus { - if (isIdle(session.lastActive)) { - return AgentStatus.IDLE; - } - if (session.lastMessageType === "gemini" || session.lastMessageType === "assistant") { return AgentStatus.WAITING; } + if (isIdle(session.lastActive)) { + return AgentStatus.IDLE; + } + return AgentStatus.RUNNING; } diff --git a/packages/agent-manager/src/harnesses/grok/GrokSessionParser.ts b/packages/agent-manager/src/harnesses/grok/GrokSessionParser.ts index 9237a33fb..c17f02bc3 100644 --- a/packages/agent-manager/src/harnesses/grok/GrokSessionParser.ts +++ b/packages/agent-manager/src/harnesses/grok/GrokSessionParser.ts @@ -119,17 +119,17 @@ export class GrokSessionParser { /** * Determine agent status from parsed session state. * + * - last transcript turn is an assistant message → WAITING (awaiting input) * - past the idle threshold → IDLE - * - last transcript turn is an assistant message → WAITING (awaiting user) * - otherwise (last turn was a user message, or unknown) → RUNNING */ determineStatus(session: GrokSession): AgentStatus { - if (isIdle(session.lastActive)) { - return AgentStatus.IDLE; - } if (session.lastRole === "assistant") { return AgentStatus.WAITING; } + if (isIdle(session.lastActive)) { + return AgentStatus.IDLE; + } return AgentStatus.RUNNING; } diff --git a/packages/agent-manager/src/harnesses/kiro/KiroSessionParser.ts b/packages/agent-manager/src/harnesses/kiro/KiroSessionParser.ts index 53f0b8118..db76baca7 100644 --- a/packages/agent-manager/src/harnesses/kiro/KiroSessionParser.ts +++ b/packages/agent-manager/src/harnesses/kiro/KiroSessionParser.ts @@ -132,15 +132,15 @@ export class KiroSessionParser { } /** - * - past the idle threshold → IDLE * - last event is an assistant reply without a tool call → WAITING + * - past the idle threshold → IDLE * - otherwise (user prompt, tool call or tool result) → RUNNING */ determineStatus(session: KiroSession): AgentStatus { - if (isIdle(session.lastActive)) return AgentStatus.IDLE; if (session.lastEventKind === "AssistantMessage" && !session.lastAssistantHasToolUse) { return AgentStatus.WAITING; } + if (isIdle(session.lastActive)) return AgentStatus.IDLE; return AgentStatus.RUNNING; } diff --git a/packages/agent-manager/src/harnesses/opencode/OpenCodeAgentMapper.ts b/packages/agent-manager/src/harnesses/opencode/OpenCodeAgentMapper.ts index 53c0acee3..e39c00ba7 100644 --- a/packages/agent-manager/src/harnesses/opencode/OpenCodeAgentMapper.ts +++ b/packages/agent-manager/src/harnesses/opencode/OpenCodeAgentMapper.ts @@ -40,12 +40,10 @@ export class OpenCodeAgentMapper { } private determineStatus(stats: OpenCodeSessionStats, lastActive: Date): AgentStatus { - if (isIdle(lastActive)) return AgentStatus.IDLE; - - if (stats.lastRole === "assistant" && !stats.lastAssistantCompleted) { - return AgentStatus.RUNNING; + if (stats.lastRole === "assistant" && stats.lastAssistantCompleted) { + return AgentStatus.WAITING; } - if (stats.lastRole === "assistant") return AgentStatus.WAITING; + if (isIdle(lastActive)) return AgentStatus.IDLE; return AgentStatus.RUNNING; } } diff --git a/packages/agent-manager/src/harnesses/pi/PiSessionParser.ts b/packages/agent-manager/src/harnesses/pi/PiSessionParser.ts index 65ab02d9f..c058bcfb7 100644 --- a/packages/agent-manager/src/harnesses/pi/PiSessionParser.ts +++ b/packages/agent-manager/src/harnesses/pi/PiSessionParser.ts @@ -174,8 +174,8 @@ export class PiSessionParser { } determineStatus(session: PiSession): AgentStatus { - if (isIdle(session.lastActive)) return AgentStatus.IDLE; if (session.lastRole === "assistant") return AgentStatus.WAITING; + if (isIdle(session.lastActive)) return AgentStatus.IDLE; return AgentStatus.RUNNING; } diff --git a/rust/crates/devkit-harness/src/antigravity/parser.rs b/rust/crates/devkit-harness/src/antigravity/parser.rs index 3b649beb2..d8bc1d557 100644 --- a/rust/crates/devkit-harness/src/antigravity/parser.rs +++ b/rust/crates/devkit-harness/src/antigravity/parser.rs @@ -54,13 +54,13 @@ pub fn read_session(conversation_id: &str, transcript_path: &str, cwd: &str) -> }) } -/// `determineStatus`: past the idle mark → idle; last turn assistant → -/// waiting; else running. +/// `determineStatus`: last turn assistant → waiting; past the idle mark → +/// idle; else running. pub fn status(session: &Session, now_ms: i64) -> &'static str { - if shared::is_idle(session.last_active_ms, now_ms) { - "idle" - } else if session.last_role == Some("assistant") { + if session.last_role == Some("assistant") { "waiting" + } else if shared::is_idle(session.last_active_ms, now_ms) { + "idle" } else { "running" } diff --git a/rust/crates/devkit-harness/src/codex/parser.rs b/rust/crates/devkit-harness/src/codex/parser.rs index 4be5d56af..57b7985b3 100644 --- a/rust/crates/devkit-harness/src/codex/parser.rs +++ b/rust/crates/devkit-harness/src/codex/parser.rs @@ -251,11 +251,9 @@ fn to_session(state: SummaryState, file_mtime_ms: impl FnOnce() -> i64) -> Optio /// `determineStatus` pub fn determine_status(session: &Session, now_ms: i64) -> &'static str { - if shared::is_idle(session.last_active_ms, now_ms) { - return "idle"; - } match session.last_payload_type.as_deref() { Some("agent_message" | "task_complete" | "turn_aborted") => "waiting", + _ if shared::is_idle(session.last_active_ms, now_ms) => "idle", _ => "running", } } @@ -321,12 +319,25 @@ mod tests { fn idle_after_five_minutes() { let p = write_tmp(&[ &meta_line("s3", "/proj", "2026-10-08T10:00:00Z"), - r#"{"timestamp":"2026-10-08T10:01:00Z","type":"event_msg","payload":{"type":"task_complete","message":"bye"}}"#, + r#"{"timestamp":"2026-10-08T10:01:00Z","type":"event_msg","payload":{"type":"user_message","message":"go"}}"#, ]); let s = read_session(&p).unwrap(); assert_eq!(determine_status(&s, s.last_active_ms + 6 * 60_000), "idle"); } + #[test] + fn waiting_survives_idle_threshold() { + let p = write_tmp(&[ + &meta_line("s3", "/proj", "2026-10-08T10:00:00Z"), + r#"{"timestamp":"2026-10-08T10:01:00Z","type":"event_msg","payload":{"type":"task_complete","message":"bye"}}"#, + ]); + let s = read_session(&p).unwrap(); + assert_eq!( + determine_status(&s, s.last_active_ms + 6 * 60_000), + "waiting" + ); + } + #[test] fn legacy_message_field_wins_summary() { let p = write_tmp(&[ diff --git a/rust/crates/devkit-harness/src/copilot/parser.rs b/rust/crates/devkit-harness/src/copilot/parser.rs index c93ecc264..fe015bba0 100644 --- a/rust/crates/devkit-harness/src/copilot/parser.rs +++ b/rust/crates/devkit-harness/src/copilot/parser.rs @@ -279,11 +279,8 @@ pub fn read_session_dir( }) } -/// `determineStatus` — idle(5min) > waiting events > running. +/// `determineStatus` — waiting events > idle(5min) > running. pub fn determine_status(session: &Session, now_ms: i64) -> &'static str { - if shared::is_idle(session.last_active_ms, now_ms) { - return "idle"; - } if session.last_event_type.as_deref().is_some_and(|t| { matches!( t, @@ -292,6 +289,9 @@ pub fn determine_status(session: &Session, now_ms: i64) -> &'static str { }) { return "waiting"; } + if shared::is_idle(session.last_active_ms, now_ms) { + return "idle"; + } "running" } @@ -331,7 +331,7 @@ mod tests { assert_eq!(s.summary, "hello"); assert_eq!(s.last_event_type.as_deref(), Some("assistant.message")); assert_eq!(determine_status(&s, s.last_active_ms + 60_000), "waiting"); - assert_eq!(determine_status(&s, s.last_active_ms + 300_001), "idle"); + assert_eq!(determine_status(&s, s.last_active_ms + 300_001), "waiting"); } #[test] diff --git a/rust/crates/devkit-harness/src/devin/mod.rs b/rust/crates/devkit-harness/src/devin/mod.rs index c91cebbdc..a7a287621 100644 --- a/rust/crates/devkit-harness/src/devin/mod.rs +++ b/rust/crates/devkit-harness/src/devin/mod.rs @@ -261,18 +261,17 @@ fn is_acp_backend_child(proc: &AgentProc, process_by_pid: &HashMap lastRole==="assistant" (waiting) > +/// `determineStatus` — lastRole==="assistant" (waiting) > idle(5min) > /// running. fn determine_status( stats: &parser::SessionStats, last_active_ms: i64, now_ms: i64, ) -> &'static str { - if shared::is_idle(last_active_ms, now_ms) { - return "idle"; - } if stats.last_role.as_deref() == Some("assistant") { "waiting" + } else if shared::is_idle(last_active_ms, now_ms) { + "idle" } else { "running" } diff --git a/rust/crates/devkit-harness/src/gemini/parser.rs b/rust/crates/devkit-harness/src/gemini/parser.rs index f61d0a9d2..516029580 100644 --- a/rust/crates/devkit-harness/src/gemini/parser.rs +++ b/rust/crates/devkit-harness/src/gemini/parser.rs @@ -80,13 +80,11 @@ fn build_session( .into() } -/// `determineStatus` — idle(5min) > waiting(last turn assistant-side) > running. +/// `determineStatus` — waiting(last turn assistant-side) > idle(5min) > running. pub fn determine_status(session: &GeminiSession, now_ms: i64) -> &'static str { - if shared::is_idle(session.last_active_ms, now_ms) { - return "idle"; - } match session.last_message_type.as_deref() { Some("gemini") | Some("assistant") => "waiting", + _ if shared::is_idle(session.last_active_ms, now_ms) => "idle", _ => "running", } } diff --git a/rust/crates/devkit-harness/src/grok/parser.rs b/rust/crates/devkit-harness/src/grok/parser.rs index 69bdbbf8d..4878f82a1 100644 --- a/rust/crates/devkit-harness/src/grok/parser.rs +++ b/rust/crates/devkit-harness/src/grok/parser.rs @@ -110,14 +110,14 @@ pub fn read_session(session_dir: &Path, default_cwd: &str) -> Option { }) } -/// `determineStatus` — idle(5min) > last turn assistant (waiting) > running. +/// `determineStatus` — last turn assistant (waiting) > idle(5min) > running. pub fn determine_status(session: &Session, now_ms: i64) -> &'static str { - if shared::is_idle(session.last_active_ms, now_ms) { - return "idle"; - } if session.last_role.as_deref() == Some("assistant") { return "waiting"; } + if shared::is_idle(session.last_active_ms, now_ms) { + return "idle"; + } "running" } diff --git a/rust/crates/devkit-harness/src/kiro/parser.rs b/rust/crates/devkit-harness/src/kiro/parser.rs index 8a1b20fd3..c32adead3 100644 --- a/rust/crates/devkit-harness/src/kiro/parser.rs +++ b/rust/crates/devkit-harness/src/kiro/parser.rs @@ -93,15 +93,15 @@ pub fn read_session( }) } -/// `determineStatus`: past the 5-min idle mark → idle; else an assistant -/// reply without a tool call → waiting; everything else → running. +/// `determineStatus`: an assistant reply without a tool call → waiting; +/// else past the 5-min idle mark → idle; everything else → running. pub fn status(session: &KiroSession, now_ms: i64) -> &'static str { - if shared::is_idle(session.last_active_ms, now_ms) { - "idle" - } else if session.last_event_kind.as_deref() == Some("AssistantMessage") + if session.last_event_kind.as_deref() == Some("AssistantMessage") && !session.last_assistant_has_tool_use { "waiting" + } else if shared::is_idle(session.last_active_ms, now_ms) { + "idle" } else { "running" } diff --git a/rust/crates/devkit-harness/src/opencode/mod.rs b/rust/crates/devkit-harness/src/opencode/mod.rs index 248afc06b..3d1800951 100644 --- a/rust/crates/devkit-harness/src/opencode/mod.rs +++ b/rust/crates/devkit-harness/src/opencode/mod.rs @@ -141,20 +141,19 @@ impl HarnessAdapter for OpenCodeAdapter { } } -/// `determineStatus` — idle(5min) short-circuits; last assistant role: -/// not-completed → running, completed → waiting; anything else running. +/// `determineStatus` — a completed last assistant message → waiting; +/// otherwise idle(5min) → idle, else running. /// (TS's `lastAssistantErrored` flag is likewise unused there.) fn determine_status( stats: &parser::SessionStats, last_active_ms: i64, now_ms: i64, ) -> &'static str { - if shared::is_idle(last_active_ms, now_ms) { - return "idle"; - } - match stats.last_role.as_deref() { - Some("assistant") if !stats.last_assistant_completed => "running", - Some("assistant") => "waiting", - _ => "running", + if stats.last_role.as_deref() == Some("assistant") && stats.last_assistant_completed { + "waiting" + } else if shared::is_idle(last_active_ms, now_ms) { + "idle" + } else { + "running" } } diff --git a/rust/crates/devkit-harness/src/pi/parser.rs b/rust/crates/devkit-harness/src/pi/parser.rs index a36e4e9a3..b0cad0edd 100644 --- a/rust/crates/devkit-harness/src/pi/parser.rs +++ b/rust/crates/devkit-harness/src/pi/parser.rs @@ -370,13 +370,11 @@ fn apply_head_line(head: &mut SessionHead, line: &[u8]) { } } -/// `determineStatus` — idle > assistant→waiting > running. +/// `determineStatus` — assistant→waiting > idle > running. pub fn determine_status(session: &Session, now_ms: i64) -> &'static str { - if shared::is_idle(session.last_active_ms, now_ms) { - return "idle"; - } match session.last_role.as_deref() { Some("assistant") => "waiting", + _ if shared::is_idle(session.last_active_ms, now_ms) => "idle", _ => "running", } }