# AGENT_TODO — Worklist & Idea Backlog Living list of what we are doing, what is next, and what we might do later. Deep history lives in git: `git log --follow AGENT_TODO.md`, `git blame`, and the individual feature commits — the **Done** section below is just a readable summary (newest last). ## Conventions - **Now** — items actively being worked on this session (only a few). - **Next** — queued, well-scoped items. - **Later** — idea backlog. Ideas marked **NOT NOW** were evaluated and rejected for the stated reason; revisit only if circumstances change. - When a task is completed: move it from Now/Next into **Done** (dated one-line) in the same commit that finishes the work. ## Done - **2026-08-13** — Fix scrcpy apt install on the live box: preinstall `apt install` failed with `Unable to locate package scrcpy` (Debian/Ubuntu need contrib/universe for `scrcpy`, and the apt build is older anyway). Removed `scrcpy` from `preinstall.sh` PACKAGES (kept `adb`); `scrcpy` now installs via the existing optional app `apps/media/scrcpy.sh` (GitHub latest, bundles adb) — docs (POS.md, howto/communication.md) and the tool's deps-guard error reworded to lead with that path. Re-verified: `bash -n`, stub suite 21/21, `make gen && make check` green. - **2026-08-13** — `pos communication scrcpy` (`bin/pos-communication-scrcpy`): wrapper over scrcpy+adb for Android mirroring/control. Subcommands: bare `scrcpy` (mirror — config defaults + verbatim pass-through of any scrcpy flag; no device → friendly error + hints), `devices` (`adb devices -l`), `record [file] [--headless]` (default `$SCRCPY_RECORD_DIR/_.mp4`, `--headless` = `--no-playback` for headless servers), `tcpip [port]` (USB→wireless switch + prints `connect` with the auto-detected device IP), `connect ` (adb connect + mirror `-s`), `push` (default `/sdcard/Download` = scrcpy's own default), `pull`, `screenshot` (`adb exec-out screencap -p` → PNG in RECORD_DIR), `info` (model/android/sdk/serial via getprop). Config scope `scrcpy` (`~/.config/linux_post_install/scrcpy.env`, `pos config scrcpy`): `SCRCPY_SERIAL/MAX_SIZE/MAX_FPS/BIT_RATE/FULLSCREEN/RECORD_DIR/PUSH_TARGET/EXTRA_FLAGS`, env-var precedence. Deps `scrcpy` + `adb` added to preinstall PACKAGES; docs note the apt build is older and point to the existing `apps/media/scrcpy.sh` app installer (GitHub latest, bundles adb) — researched 2026 releases (current v4.1). Conventions: `# POS:`/`# POS_SUBCMDS:`/`# POS_CONFIG:` headers, deps guards before `-h|--help`, no stdin → no INTERACTIVE_CMDS. Verified: `bash -n`, stub-PATH suite `/tmp/opencode/scrcpy-run-test.sh` 21/21 green (fake adb/scrcpy echo-args, HOME isolation, env/file precedence, rc paths, screenshot bytes), `make gen && make check` green, dispatch via `pos communication scrcpy --help`. Docs: POS.md communication table + detail block, howto/communication.md section, HOWTO.md index + env row, AGENT_Context Common Tasks row + gen'd tree/dispatch/filetable. - **2026-08-13** — Entertainment-module hardening (approved Tier 1 + Tier 2): delivery moved to `notify_send` (platform follows `NOTIFY_PLATFORM`, default Telegram) via `lib/notify.sh` sourced by `bin/pos-entertainment-send`; a **last-run state** is recorded per plugin (`~/.local/share/linux_post_install/entertainment/last/` — rc + timestamp) on every non-`--print` run and shown by `pos entertainment status`, which also lists installed-but-not-enabled plugins; a send that fails **while fired by a timer** (gated on `$INVOCATION_ID`) additionally notifies the configured platforms. New **message-safe plugin lib** `lib/entertainment-plugin-lib.sh` (defines only `plugin_*`, never writes stdout — the stdout contract stays "message only"): `plugin_load_config` (entertainment.env + env precedence), `plugin_have`, `plugin_require`, `plugin_err`, `plugin_http_json [--key ] [-H
]` (curl `--max-time 20 --retry 2`); `weather`/`joke`/`gold` refactored onto it. `pos entertainment config` gains `get|unset|ls|edit` (edit via the shared `pos config` UI — added to `INTERACTIVE_CMDS`). **Tier 2**: new shared lib `lib/user-timers-lib.sh` (only `ut_*`: `ut_interval_to_oncalendar`, `ut_interval_label`, `ut_unit_name`, `ut_write_unit_pair` incl. `TimeoutStopSec=5s` + `Persistent` + network-online deps, `ut_ensure_linger`, `USER_SYSTEMD_DIR`) dedupes the systemd user-timer machinery between `lib/entertainment-lib.sh` and `lib/scheduler-lib.sh` (the latter's `sched_*` duplicates deleted; both source it; collides-with-nothing). `install.sh` Phase 2 lib list += the two new libs; SCRIPTS.md/DEV.md/POS.md/howto/entertainment.md/AGENT_Context updated (hand-maintained lib rows: entertainment-lib 354→311, scheduler-lib 830→760, +112 user-timers-lib, +67 plugin lib). Verified: `bash -n` everywhere; smoke-tested in an isolated `HOME=/tmp/enttest` (status, config get/set/unset/ls, send path rc=0, failing plugin records rc=1, error-case message hygiene); `make gen && make check` green. - **2026-08-13** — Fast pos-unit shutdown: every systemd unit a pos tool writes (or `systemd/` ships) now sets `TimeoutStopSec=5s` (+ `KillMode=control-group` on the daemons) so a stuck process can't stall a reboot for the 90s systemd default. Applied at all 7 template sites: `pos-communication-telegram-listener`, `pos-communication-matrix-listener` (also gained a `trap 'kill $(jobs -p) 2>/dev/null; exit 0' TERM INT` in `run_daemon` so stop returns sub-second), `pos-network-download` (aria2 + retry-healer units), `lib/scheduler-lib.sh` `sched_write_units`, `lib/entertainment-lib.sh` `write_units`, and `systemd/{ssh-agent,autostart,usb-automount}.service`. Legacy-unit cleanup: the repo no longer ships `pos-health.{service,timer}` / `pos-entertainment.service` (they were documented but postinstall never created them — found stale only on the live box, FAILED); removed their stale references from SYSTEMD.md (deleted the `pos-health.service` section + gating special-case, added a new **Stop behavior** section), POS.md, HOWTO.md, howto/system.md (now documents the `pos system schedule` job replacement + removal commands), AGENT_Context (tree, phase description, selfcontained table). DEV.md Best Practices gains a **Systemd units** convention (TimeoutStopSec=5s + TERM trap + regeneration caveat). Verified: `bash -n` on all edited scripts; `make gen && make check` green (filetable rows for the two listeners + network-download auto-regenerated, hand-maintained lib rows bumped 350→354 / 822→830). Live-box application is manual (this session was a Google Cloud Shell, not the real machine): regenerate units via `pos network download start`, `pos communication telegram listener --enable`, `pos system schedule enable `, `pos entertainment enable `, then `sudo systemctl disable --now pos-health.timer pos-health.service 2>/dev/null; sudo rm -f /etc/systemd/system/pos-health.{service,timer} && sudo systemctl daemon-reload`. - **2026-08-13** — Bootstrap output transparency (`install.sh` / `preinstall.sh` / `postinstall.sh`): removed the redundant `apt update` (preinstall.sh owns it — install.sh previously ran it twice, showing two identical `OK apt update` lines); Phase 2 now names what it installs — libs line (`libs -> /usr/local/bin (644): common.sh flags.sh …`), plugin names in the count line, x64_bin names, per-feature `feature installed/overwritten` + `feature flag set` logs with a `N features installed: …` summary — and the misleading `"47 scripts + libs"` label is fixed to `47 scripts + 6 libs` (the 6 libs were outside the counter); preinstall prints `Installing N packages (apt install -y):` with the 40-name list wrapped at 80 cols; postinstall now logs silent skips — `config/authorized_keys is empty — nothing to add` (empty file previously looped zero times with no message), `schedule.d already exists, keeping it` (restructured the condition so the message is accurate when the dest exists vs config/schedule.d absent), and a per-service `service enabled: ` line. No output-layer changes (no `--verbose`, no log file — decided scope). Verified: `bash -n` + `--dry-run` smokes of phases 1/2/3 showing every new line (learned: `install.sh:19` hardcodes `export DRY_RUN=0`, so an env `DRY_RUN=1` is ignored — the flag `--dry-run` is required), hand-maintained filetable count rows bumped (install.sh 206→223, preinstall.sh 73→75, postinstall.sh 163→168), `make gen && make check` green. usb-automount left live (user choice). - **2026-08-13** — `usb-automount` feature, integrated exactly like `autostart`: `features/usb-automount.sh` (root-guard re-exec via sudo; first-root-run self-install of udev rule `/etc/udev/rules.d/99-usb-automount.rules` — `ACTION=="add", KERNEL=="sd[a-z]*", SUBSYSTEM=="block", ENV{ID_BUS}=="usb", TAG+="systemd", SYSTEMD_WANTS="usb-automount.service"` — + `udevadm control --reload` + `trigger --subsystem-match=block`; an existing/edited rule is never overwritten; scans `lsblk -J` for unmounted removable partitions/raw whole-disk filesystems, mounts each at `/media/