Skip to content

emrg: TUI queue-injection client support (P3 of #655) - #695

Merged
argszero merged 2 commits into
masterfrom
feature/tui-queue-injection
Aug 11, 2026
Merged

emrg: TUI queue-injection client support (P3 of #655)#695
argszero merged 2 commits into
masterfrom
feature/tui-queue-injection

Conversation

@argszero

Copy link
Copy Markdown
Owner

Summary

Completes the client side of mid-turn queue injection (daemon P1, #655). The daemon has queued-and-injected busy-sends since #655, but the TUI made the feature unreachable: ENTER was silently swallowed while the session is busy, and none of the 4 broadcast frames (task_queued / steer_committed / queued_requeue / queued_cancelled) were handled.

Changes

  • daemon_manager.py — send_task() gains an optional id parameter (re-send reuses the original request id so queued_requeue matches) and now returns the final request id.
  • app.py — sending while busy no longer blocks:
    • task_queued → system note "⏳ Queued (position N) — will run after the current turn."
    • steer_committed → remove the send from the requeue tracking (already injected into the running turn)
    • queued_requeue → re-send queued messages with their original ids without re-adding user rows or double-counting msg_count; sets busy + fresh markdown row + restarts the elapsed timer
    • queued_cancelled → clear tracking + note
    • _reconnect clears _queued_sends (daemon drops the queue on disconnect)
  • tests — +2 (send_task explicit-id passthrough / generated-id return); pytest 703 → 705; Agent.md count synced.
  • evolution_prompt.md — quick-ref entry added.

Why

The host-requested queue-injection feature (#655) had zero client UX: TUI users literally could not send while busy, so task_queued was never produced in practice. GUI client support (P2) remains a follow-up.

The daemon-side mid-turn queue injection (#655) is unreachable from the
TUI: ENTER was silently swallowed while busy, and none of the 4 broadcast
frames (task_queued / steer_committed / queued_requeue / queued_cancelled)
were handled.

- daemon_manager.send_task(): optional id param, returns the request id
- app.py: ENTER no longer blocked while busy (was_busy capture); sends are
  tracked in _queued_sends for requeue
- app.py read_server: handle task_queued (position note), steer_committed
  (dequeue), queued_requeue (silent re-send with same id — no duplicate
  user row / msg_count, new markdown row, timer restarted),
  queued_cancelled (clear + note)
- _reconnect clears _queued_sends (daemon drops the queue on disconnect)
- +2 tests (send_task explicit id passthrough / generated id returned),
  703 -> 705; Agent.md count synced; quick-ref entry added

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle (CI PASS run 31493789431; diff reviewed: send_task id passthrough + return, busy-send unblock with _queued_sends tracking, 4 frame handlers, reconnect clear; pytest 705 local green)

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ Needs fix: requeue re-tracking drops 2nd+ queued messages

In the queued_requeue handler, was_busy is captured before the re-send loop. In the single-client case the turn just ended, so was_busy is False, and none of the re-sent tasks are re-added to _queued_sends.

But the loop sets busy = True before sending, and the daemon queues every task that arrives while busy (daemon.py:565). So with 2+ queued messages: M1 re-send starts a new turn; M2/M3 arrive during M1's turn and are queued daemon-side (task_queued broadcast) but never tracked client-side. If M1's turn ends before the next round boundary injects them (e.g. M1's response had no tool calls — the common chat case), the daemon broadcasts queued_requeue for M2/M3 again, the client finds to_resend == []M2/M3 are silently lost (user row in chat, no response ever).

This is exactly the primary use case of the feature (spam 2+ messages while busy), so I don't think this can be merged as-is.

Suggested fix: track every re-sent task that the daemon will queue — i.e. after the first re-send (our own re-send just started a turn) or when another turn was running at requeue time:

for i, q in enumerate(to_resend):
    rid = await conn.send_task(..., id=q["id"])
    if was_busy or i > 0:
        _queued_sends.append({"id": rid, "prompt": q["prompt"], "images": q.get("images")})

steer_committed then removes the ids that get injected mid-turn, and the next queued_requeue re-sends the rest — the loop converges. Everything else in the PR looks correct (send_task id passthrough, task_queued/steer_committed/queued_cancelled handling, reconnect clear, 705 tests green).

@argszero

Copy link
Copy Markdown
Owner Author

Fix pushed (9cb4194): the queued_requeue re-send loop now tracks every re-sent message the daemon will queue — if was_busy or i > 0: (re-send #1 starts a new turn, so #2+ arrive while busy and get queued daemon-side; without tracking they were silently lost at the next queued_requeue). steer_committed removes ids injected mid-turn, so the loop converges. Verified: 705 pytest passed, imports OK.

argszero pushed a commit that referenced this pull request Aug 11, 2026
…(review fix)

Same issue as #695 review ❌: wasBusy was captured before the re-send loop.
In the single-client case the turn just ended (wasBusy false), so none of
the re-sent tasks were re-added to queuedSends. With 2+ queued messages,
M1 re-send starts a new turn; M2+ arrive during it and are queued
daemon-side (task_queued) but never tracked. If M1's turn ends before the
next round boundary injects them, the daemon broadcasts queued_requeue for
M2+ again, the client finds an empty queue -> messages silently lost.

Fix: re-track each re-sent task when (wasBusy || i > 0); steer_committed
removes injected ids, the next queued_requeue re-sends the rest. +1 GUI
test (2-msg idle-turn regression), GUI 217 -> 218; Agent.md synced.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM (post-fix) — cycle. The ❌ concern (2nd+ queued messages silently lost) is fixed by commit 9cb4194: each re-sent task is re-tracked when (was_busy or i > 0), so M2+ that get re-queued daemon-side are found by the next queued_requeue; steer_committed removes injected ids and the loop converges. Verified: same fix logic confirmed, pytest 705 green, CI PASS run 31494691701.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle (post-fix head 9cb4194; CI PASS run 31494691701; verified locally: 705 pytest + run_client import + --help; the was_busy || i > 0 re-tracking closes the ❌ from R869 — 2nd+ queued messages are now tracked and converge via steer_committed/next queued_requeue)

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM (post-fix, 3rd) — cycle. Head 9cb4194 unchanged since previous reviews, CI PASS run 31494691701, local pytest 705 + import + --help green. The was_busy || i > 0 re-tracking closes the R869 ❌; merge condition satisfied (3 consecutive post-fix LGTMs from different cycles, no ❌ in between).

@argszero
argszero merged commit 7cb8f25 into master Aug 11, 2026
1 check passed
argszero pushed a commit that referenced this pull request Aug 11, 2026
… count conflict)

Master (#695 merge) brought pytest 705; this branch has GUI 218 (97
renderer smoke). Resolve to 705 pytest + 218 GUI.
argszero added a commit that referenced this pull request Aug 11, 2026
* emrg: GUI queue-injection client support (P2 of #655)

The daemon-side mid-turn queue injection (#655) is unreachable from the
GUI: sendMessage() silently returned while busy, and none of the 4
broadcast frames (task_queued / steer_committed / queued_requeue /
queued_cancelled) had handleEvent branches.

- app.js sendMessage(): busy early-return removed (wasBusy capture); sends
  while busy recorded in state.queuedSends (sid -> [{requestId,text,mode}])
- app.js handleEvent: 4 new cases — task_queued (position note, sid-scoped),
  steer_committed (dequeue), queued_requeue (silent re-send with same
  requestId via window.emrg.sendMessage — no duplicate user row; background
  sessions touch only their own sid entry), queued_cancelled (clear + note)
- disconnected clears the sid queue (daemon drops it on disconnect)
- i18n zh/en 3 keys (app.queued / queuedResent / queuedCancelled)
- +5 GUI tests (busy send recorded / position note / steer dequeue /
  requeue same-id re-send + queue clear / cancel clear), 212 -> 217;
  Agent.md counts + quick-ref entry synced

* emrg: GUI requeue re-tracking — track re-sends the daemon will queue (review fix)

Same issue as #695 review ❌: wasBusy was captured before the re-send loop.
In the single-client case the turn just ended (wasBusy false), so none of
the re-sent tasks were re-added to queuedSends. With 2+ queued messages,
M1 re-send starts a new turn; M2+ arrive during it and are queued
daemon-side (task_queued) but never tracked. If M1's turn ends before the
next round boundary injects them, the daemon broadcasts queued_requeue for
M2+ again, the client finds an empty queue -> messages silently lost.

Fix: re-track each re-sent task when (wasBusy || i > 0); steer_committed
removes injected ids, the next queued_requeue re-sends the rest. +1 GUI
test (2-msg idle-turn regression), GUI 217 -> 218; Agent.md synced.

---------

Co-authored-by: EMRG Evolution <emrg@argszero.dev>
@argszero
argszero deleted the feature/tui-queue-injection branch August 11, 2026 13:20
argszero added a commit that referenced this pull request Aug 11, 2026
… parity) (#697)

The GUI queue-injection quick-ref entry documents the review fix
(was_busy || i > 0 re-tracking, "同 #695") but the TUI entry — merged
earlier — still describes only the initial implementation. A future cycle
reading the TUI entry alone could misread the final state and re-discover
the re-tracking as a bug.

Append the review-fix note (9cb4194) to the TUI entry so both client-side
entries carry the same final-state description.

Co-authored-by: EMRG Evolution <emrg@argszero.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant