Files
kandev/apps
João Salavisa 616bafa848 fix(backend): allow IDLE office sessions to accept follow-up prompts (#1119)
* fix(backend): allow IDLE office sessions to accept follow-up prompts

The IDLE session state (introduced with the office feature) was missing from
checkSessionPromptable's accept bucket, so any prompt to an IDLE office
session fell into the default branch and returned ErrAgentPromptInProgress
with the misleading message "agent is currently processing a prompt, session
is in IDLE state" — even though IDLE means the agent process is in fact gone
(torn down between turns) and the conversation is parked waiting to resume.

Net effect before this fix: once an office session went IDLE it could not be
re-engaged from either the UI composer or MCP message_task_kandev. The five
IDLE sessions sitting in the local DB exhibit this exactly.

Fix:
- Add TaskSessionStateIdle to the accept bucket in checkSessionPromptable.
  ensureSessionRunning already does the right thing for IDLE: it detects the
  missing in-memory execution, finds the preserved executors_running row,
  and routes through ResumeSession (which uses the stored ACP resume token).
- Introduce ErrSessionNotPromptable for STARTING / CREATED / FAILED /
  CANCELLED rejections. The previous code wrapped those in
  ErrAgentPromptInProgress, which is semantically wrong and breaks any
  caller doing errors.Is(err, ErrAgentPromptInProgress) checks (the MCP
  promptWithAutoResume retry path keys on a different sentinel, so this
  doesn't regress, but the categorisation is now honest).

Tests:
- TestPromptTask_IdleSession_IsPromptable — reproduces the bug end-to-end.
- TestCheckSessionPromptable_StateMatrix — pins down every state's
  classification, including the IDLE→accept and the
  STARTING/CREATED/FAILED/CANCELLED → ErrSessionNotPromptable (NOT
  ErrAgentPromptInProgress) split.
- TestEnsureSessionRunning_IdleSessionTriggersResume — confirms IDLE
  sessions reach ResumeSession (the second half of the recovery path).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: preserve requeue for STARTING/CREATED + drop time.Sleep in test

Address PR #1119 review feedback from Greptile, cubic, and Claude:

P1: ErrSessionNotPromptable broke the requeue guard in executeQueuedMessage
and the auto-start retry loop. Pre-PR, STARTING/CREATED bucketed into
ErrAgentPromptInProgress and matched isAgentPromptInProgressError, so
queued messages hitting a booting session were retried; post-PR they would
have been silently dropped (no DLQ — see TODO at event_handlers_agent.go).

Introduce isSessionBusyError covering both sentinels and swap it into the
two requeue call sites. Pinned by TestIsSessionBusyError.

P2: Replace the 50ms time.Sleep in TestEnsureSessionRunning_IdleSessionTriggersResume
with a deterministic state-watching poll (waits for STARTING from
persistResumeState before flipping to WAITING_FOR_INPUT). No CI flake
exposure, no timing assumption.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: replace test poll loop with ticker+select per AGENTS.md

Address CodeRabbit + Claude review on PR #1119: the 5ms time.Sleep poll
loop in the launchAgentFunc watcher goroutine flunked the backend
convention of "channel-based synchronization over sleep-based waits."
testing/synctest doesn't help here (SQLite repo runs real I/O goroutines
that synctest can't observe as idle), so use a ticker + select instead,
keeping the 5s timeout as the safety bound.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 21:40:15 +01:00
..