Skip to content

M5 (1/3): start at logon as a scheduled task, keep the computer awake - #10

Merged
httpsphl merged 1 commit into
mainfrom
feat/m5-service
Sep 28, 2026
Merged

httpsphl merged 1 commit into
mainfrom
feat/m5-service

Conversation

@httpsphl

Copy link
Copy Markdown
Owner

First slice of M5 (spec section 17): the daemon side of "bots come back on their own after a reboot".

What changes

  • botloftd service install | status | restart | uninstall manages a per-user scheduled task through the Task Scheduler COM API (schtasks.exe output is localized, so it cannot be parsed).
    • A logon trigger starts the daemon at logon. A time trigger that started in the past and repeats every minute, with IgnoreNew, brings it back within a minute if it dies.
    • The task runs as the current user with an interactive token and least privilege, so it keeps the owner's Claude Code sign-in. No time limit, runs on battery, priority 5: the default (7) would hand "below normal" down to every bot.
    • install copies the running binary to <home>\bin\botloftd.exe and the task runs that copy. This way the app's NSIS installer can replace its own sidecar while the daemon runs (next PR). A busy exe gets renamed aside instead of overwritten. If the same binary already runs from the task, install leaves it alone and the bots keep going.
    • Each data folder gets its own task (Botloft, or Botloft-<hash> for a dev BOTLOFT_HOME). A new global --home flag overrides BOTLOFT_HOME; the task passes it.
  • Manifest: longPathAware and consoleAllocationPolicy = detached, so a daemon started by the task gets no console window. It is embedded as a resource (embed-resource) because the linker's manifest tool warns about the new element. On Windows before 24H2, serve calls FreeConsole when it is the only process on its console.
  • Keep-awake: a power request (PowerCreateRequest/PowerSetRequest) is held while any bot is busy and keep_awake = true. The supervisor now tracks a busy count, and a new test covers turns, approvals and crashes.
  • Fatal start-up errors now go to the log file, since nobody sees the task's stderr.

Tested on Windows 11 25H2 (recorded in spec 14)

  • RestartOnFailure does not restart a task whose process exits with an error code, neither after a time trigger nor after an on-demand run. I checked this with throwaway tasks, which are now deleted.
  • A logon trigger's repetition only starts at the next logon. The first version relied on it, and a killed daemon stayed down. With the repeating time trigger, a killed daemon was back in 43 s.
  • install → running with no console host attached; restart → back in about 4 s; running install again → "already running", nothing restarted; uninstall → task and binary gone, data kept; restart without a task → clear error.

Manual test (needs a real logon)

  1. botloftd service install (default data folder, or --home for a scratch one).
  2. Sign out and back in, or reboot: botloftd service status shows the task running and the daemon answering; bots are back.
  3. In an elevated terminal, with a bot mid-turn: powercfg /requests lists botloftd.exe under SYSTEM with "Botloft bots are working", and the entry goes away when the turn ends.
  4. botloftd service uninstall.

…r awake (M5)

`botloftd service install|status|restart|uninstall` registers a per-user
scheduled task through the Task Scheduler COM API (schtasks output is
localized). A logon trigger starts the daemon at logon; a time trigger
that repeats every minute with IgnoreNew brings it back after a crash.
Tested on Windows 11 25H2: RestartOnFailure does not restart a process
that exits with an error, and a logon trigger's repetition only begins
at the next logon, so neither can be the watchdog.

`service install` copies the running binary to <home>\bin and the task
runs that copy, so the app's installer can replace its own files while
the daemon runs. A busy exe is renamed aside instead of overwritten.
One task per data folder, so a dev BOTLOFT_HOME never replaces the real
installation; `--home` overrides BOTLOFT_HOME for every subcommand.

The daemon's manifest asks for long paths and no console
(consoleAllocationPolicy), embedded as a resource because the linker's
manifest tool warns about the new element. Older Windows gets FreeConsole.
Fatal start-up errors now reach the log file, since nobody sees the
task's stderr.

Keep-awake holds a power request while any bot is busy. Unlike
SetThreadExecutionState it belongs to a handle, not a thread, which fits
Tokio's worker pool. The spec is updated to match.
@httpsphl
httpsphl merged commit ad750df into main Sep 28, 2026
3 checks passed
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