Context
This issue was found while developing and repeatedly debugging the FishingOnceTask in ok-how-to-fish. ok-how-to-fish was created from the ok-script application template and uses ok-script as its underlying task execution framework.
The task uses the documented BaseTask API. Its run() method starts by calling ensure_in_front() before sending keyboard and mouse input. The expected debugging workflow is to run the task, adjust the task code or configuration, then run the same task again.
This is not caused by the application template using the task API incorrectly or by the GUI debug action calling the task incorrectly. The GUI run action calls StartController.start(task), which is the framework's normal entry point for starting a one-time task.
Reproduction
- On Windows, configure a game with a foreground interaction backend such as
Pynput or PyDirect.
- Register a one-time task whose
run() starts with ensure_in_front() and then performs input. The FishingOnceTask in ok-how-to-fish is an example.
- Start the task once while the game window is active. The task runs successfully.
- Let the client UI or another application take focus.
- Start the same one-time task again, for example from the GUI task card or the task editor's Run action.
Expected behavior
The target game window is brought to the foreground before the executor requests the first frame, and the task enters task.run() normally on every run.
Actual behavior
The task is queued and selected, but it does not reach its first task-level action immediately. For example, the log does not show the FishingOnceTask state Selecting rod until after the frame wait/timeout.
The current execution order is effectively:
select one-time task
-> request next_frame()
-> task.run()
For foreground interaction backends, should_capture() returns False while the target window is not active. Therefore next_frame() waits before task.run() is reached. The task-level ensure_in_front() cannot prepare the window because it is after the blocked operation. In the current executor, the one-time path passes time_out=4, so this appears as an avoidable delay of up to about four seconds rather than a true infinite wait.
There is another relevant framework path: StartController._mark_task_enabled() directly marks the task enabled and queues it. It does not go through BaseTask.enable(), where interaction.on_run() is otherwise called. This path is used by the GUI task card/editor flow and also by command-line/headless and Web/API task starts.
Root cause
The framework prepares the foreground interaction too late for one-time tasks. A one-time task must be prepared after it is selected but before the first next_frame() call.
The task API usage is valid: ensure_in_front() is a reasonable task-level action, but the executor's precondition that it must obtain a frame first makes that action unable to help in this case.
Trigger tasks should keep their existing behavior because they are expected to observe the current foreground/capture state rather than forcibly foregrounding the target for every trigger check.
Proposed fix
Prepare the interaction backend before the first frame for one-time tasks. The follow-up PR implements this at the TaskExecutor boundary and adds a regression test for the execution order. The preparation is a no-op for interaction backends that do not need foreground activation.
Environment
- Windows 11
- ok-script 2.0.0b7
- Python 3.12
Pynput interaction backend
- Reproduced while developing
ok-how-to-fish
Context
This issue was found while developing and repeatedly debugging the
FishingOnceTaskin ok-how-to-fish.ok-how-to-fishwas created from theok-scriptapplication template and usesok-scriptas its underlying task execution framework.The task uses the documented
BaseTaskAPI. Itsrun()method starts by callingensure_in_front()before sending keyboard and mouse input. The expected debugging workflow is to run the task, adjust the task code or configuration, then run the same task again.This is not caused by the application template using the task API incorrectly or by the GUI debug action calling the task incorrectly. The GUI run action calls
StartController.start(task), which is the framework's normal entry point for starting a one-time task.Reproduction
PynputorPyDirect.run()starts withensure_in_front()and then performs input. TheFishingOnceTaskinok-how-to-fishis an example.Expected behavior
The target game window is brought to the foreground before the executor requests the first frame, and the task enters
task.run()normally on every run.Actual behavior
The task is queued and selected, but it does not reach its first task-level action immediately. For example, the log does not show the
FishingOnceTaskstateSelecting roduntil after the frame wait/timeout.The current execution order is effectively:
For foreground interaction backends,
should_capture()returnsFalsewhile the target window is not active. Thereforenext_frame()waits beforetask.run()is reached. The task-levelensure_in_front()cannot prepare the window because it is after the blocked operation. In the current executor, the one-time path passestime_out=4, so this appears as an avoidable delay of up to about four seconds rather than a true infinite wait.There is another relevant framework path:
StartController._mark_task_enabled()directly marks the task enabled and queues it. It does not go throughBaseTask.enable(), whereinteraction.on_run()is otherwise called. This path is used by the GUI task card/editor flow and also by command-line/headless and Web/API task starts.Root cause
The framework prepares the foreground interaction too late for one-time tasks. A one-time task must be prepared after it is selected but before the first
next_frame()call.The task API usage is valid:
ensure_in_front()is a reasonable task-level action, but the executor's precondition that it must obtain a frame first makes that action unable to help in this case.Trigger tasks should keep their existing behavior because they are expected to observe the current foreground/capture state rather than forcibly foregrounding the target for every trigger check.
Proposed fix
Prepare the interaction backend before the first frame for one-time tasks. The follow-up PR implements this at the
TaskExecutorboundary and adds a regression test for the execution order. The preparation is a no-op for interaction backends that do not need foreground activation.Environment
Pynputinteraction backendok-how-to-fish