From 90db743922924f13b0e75874f885eba9362a8953 Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Wed, 10 Jun 2026 22:39:44 -0400 Subject: [PATCH] docs: design delayed prompt injection --- ...6-06-11-delayed-prompt-injection-design.md | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-11-delayed-prompt-injection-design.md diff --git a/docs/superpowers/specs/2026-06-11-delayed-prompt-injection-design.md b/docs/superpowers/specs/2026-06-11-delayed-prompt-injection-design.md new file mode 100644 index 0000000..465d70f --- /dev/null +++ b/docs/superpowers/specs/2026-06-11-delayed-prompt-injection-design.md @@ -0,0 +1,198 @@ +# Delayed prompt injection + +**Date:** 2026-06-11 +**Status:** Approved + +## Goal + +Add a TUI-only feature that schedules one delayed prompt for a specific hqt +session and injects it when the selected time arrives. The primary use case is +recovering a harness that stopped because it ran out of budget: the user can +schedule a restart/continue prompt and let hqt deliver it later. + +Scheduled prompts fire only while the hqt TUI is running. If hqt is closed at +the due time, the prompt remains pending and fires on the next TUI poll after +hqt starts again. + +## User Behavior + +The session list gets a new keybinding, `p`, labeled "Prompt Later". Pressing +it for the selected session opens a modal that collects: + +- Prompt text. +- A relative delay such as `30m`, `2h`, `1h30m`, or `90s`. +- A confirmation action. + +Each session supports exactly one active delayed prompt. Scheduling a new prompt +for that session replaces the existing pending, sent, or failed prompt. A +companion cancel action, `P` / `Shift+P`, removes the selected session's active +prompt. + +Visualization is intentionally compact: + +- The session row shows the next pending trigger, for example + `◉ waiting @ 14:30` or `● idle @ 12m`. +- After scheduling, canceling, sending, or failing a prompt, the TUI shows a + short notification such as `Prompt scheduled for hqt-4 at 14:30`. + +The row indicator is shown only for pending prompts. Sent prompts disappear from +the active indicator. Failed prompts are not retried automatically; they can be +replaced or canceled from the selected session. + +## Architecture + +Use a persisted database table and drive dispatch from the existing TUI poller. +This matches the current service architecture and keeps scheduling state visible +across TUI restarts without introducing a background daemon. + +### Data Model + +Add a `ScheduledPrompt` model and migration: + +- `id` primary key. +- `session_id` foreign key to `sessions.id`, unique. +- `prompt` text, required. +- `due_at` datetime, required. +- `status` string, one of `pending`, `sending`, `sent`, or `failed`. +- `error` text, nullable. +- `created_at` datetime. +- `sent_at` datetime, nullable. + +The unique `session_id` constraint enforces one active prompt record per +session. Scheduling a prompt replaces any existing prompt row for that session, +including failed or sent rows. + +### Service Layer + +`SessionService` owns delayed prompt behavior because it already owns session +lookup, resume/fallback behavior, status polling, and tmux interaction. + +Add service methods along these lines: + +```python +def schedule_prompt(self, session_id: int, prompt: str, due_at: datetime) -> ScheduledPrompt: + ... + +def cancel_scheduled_prompt(self, session_id: int) -> None: + ... + +def get_scheduled_prompt(self, session_id: int) -> ScheduledPrompt | None: + ... + +async def dispatch_due_prompts(self, now: datetime) -> list[DispatchResult]: + ... +``` + +Extend `SessionInfo` with an optional `scheduled_prompt` field so `SessionList` +can render the compact row indicator from the same object it already receives +for each row. + +### Tmux Layer + +`TmuxRunner` already has literal `send_text()` and `send_enter()` methods. Add +thin `TmuxManager` delegates and make both send paths return success/failure +instead of fire-and-forget so the service can record failed injection. + +Prompt injection sends the prompt body as literal text, then sends Enter as a +separate tmux key event. Prompt content such as `-flag`, quotes, semicolons, or +the word `Enter` must be treated as text, not as tmux commands or key names. + +### TUI Runtime + +The existing 3-second TUI poll loop becomes the scheduler runtime: + +1. Call `dispatch_due_prompts(now)`. +2. Surface any sent/failed dispatch results as notifications. +3. Refresh session rows and tmux labels using the existing status flow. + +No prompt fires while the TUI process is not running. Overdue prompts fire on +the first poll after startup. + +## Dispatch Semantics + +When a pending prompt is due: + +1. Load the target session row. +2. Change the prompt status to `sending` and commit before interacting with + tmux, so overlapping poll workers cannot inject the same prompt twice. +3. If the session row is gone, mark the prompt `failed`. +4. If the tmux window is alive, inject directly. +5. If the window is dead or missing, resume the session without switching tmux + focus, then inject. +6. On successful text + Enter injection, mark the row `sent` and set `sent_at`. +7. On any failure, mark the row `failed` and store an error message. + +The current `attach_session()` method resumes and then selects the tmux window. +Factor an internal helper, `_ensure_session_alive(db, sess) -> bool`, so due +prompt dispatch can reuse the resume/fallback ladder without changing the +user's active tmux window. + +Failed prompts must not retry automatically on every poll. This avoids sending +duplicate prompts or repeatedly restarting a harness after a transient failure. +The user can replace the failed prompt by scheduling a new one, or clear it with +the cancel action. + +## TUI Details + +Add a new modal screen, `SchedulePromptScreen`, under `src/hqt/tui/screens/`. +It should follow existing modal conventions from the project: + +- Plain Textual form controls. +- Compact dialog styling using existing CSS patterns. +- Inline validation for empty prompt or invalid delay. +- OK and Cancel actions. + +Delay parsing is relative-only for the first version. Accepted units are +seconds, minutes, and hours. Combined values such as `1h30m` are accepted. +Absolute timestamps, recurring prompts, and multiple queued prompts are out of +scope. + +`SessionList` row rendering includes pending prompt information when present. +Show `@ ` while the due time is less than 60 minutes away, rounded +up to whole minutes, for example `@ 12m`. Show local 24-hour time for prompts +that are at least 60 minutes away, for example `@ 14:30`. + +## Error Handling + +- Scheduling with no selected session shows a warning notification. +- Empty prompt text or invalid delay keeps the modal open with an inline error. +- Scheduling for a deleted session raises `ServiceError` and shows a TUI error. +- Deleted target session at dispatch time marks the prompt failed. +- Resume/fallback failure marks the prompt failed with the existing tmux/spawn + error when available. +- Text or Enter injection failure marks the prompt failed. +- Database errors are allowed to surface through existing service error handling. + +## Testing + +Add focused tests at the same layers the current code uses: + +- Model/migration test for the `scheduled_prompts` table and unique + `session_id`. +- Service tests for schedule, replace, cancel, and retrieving pending prompt + metadata in session rows. +- Service dispatch tests for alive-window injection, dead-window auto-resume + without attach/select, missing session failure, injection failure, and failed + prompts not retrying on the next poll. +- Tmux tests for manager send delegates and literal send behavior returning + success/failure. +- TUI tests for the keybindings, modal validation, scheduling action, cancel + action, row indicator, and poll-time dispatch notifications. + +After implementation changes, the project checks must pass: + +```bash +uv run ruff format src tests +uv run ruff check src tests +uv run ty check +``` + +## Out of Scope + +- Firing delayed prompts while hqt is closed. +- CLI scheduling. +- Multiple queued prompts per session. +- Absolute date/time scheduling. +- Recurring prompts. +- Prompt history or a full queue-management screen. +- Harness-specific prompt templates.