Skip to content

feat(adhdo2): P2 quality+personalization backlog and P3 Telegram bridge + read-only dashboard#117

Open
adrianwedd wants to merge 12 commits into
mainfrom
adhdo2-p2p3
Open

feat(adhdo2): P2 quality+personalization backlog and P3 Telegram bridge + read-only dashboard#117
adrianwedd wants to merge 12 commits into
mainfrom
adhdo2-p2p3

Conversation

@adrianwedd

Copy link
Copy Markdown
Owner

Ultracode round covering the implementable remainder of #114 (quality backlog + personalization loop) and all of #115 (Telegram bridge + dashboard). Six parallel implementation streams, then a 5-lens adversarial review (24 agents) whose 11 distinct confirmed findings were all fixed.

P2 — quality backlog (#114)

  • envelope catches Exception instead of BaseException (KeyboardInterrupt/SystemExit propagate)
  • TTS cache prune runs on every nudge invocation, race-tolerant (FileNotFoundError guarded)
  • Heartbeat moved from */25 cron (10-min hour-boundary gap) to adhdo-heartbeat.timer systemd user timer (OnUnitActiveSec=25min) — true 25-min cadence; install-cron.sh installs it
  • deploy.sh installs piper-tts idempotently and copies *.timer units
  • cast: stop journals + clears now_playing.json even when the device is undiscoverable; uniform device-not-found envelopes; logical device name reported alongside real name; Jellyfin fallthrough pinned by tests; volume arg parsing hardened
  • _parse_at accepts full ISO-8601 / date-only / HH:MM[:SS] with rollover, usage errors instead of crashes; quiet-hours boundary semantics pinned ([start, end), wrap-around) by tests
  • docs/catchup-vs-schedule.md documents catch-up vs dispatcher interplay

P2 — personalization loop (#114)

  • journal outcome <intervention> <worked|partial|ignored|backfired> [feedback]
  • journal patterns now surfaces outcome_by_intervention / by_daypart / success rates (30-day window)
  • journal rollup + scripts/adhdo-rollup.sh: nightly aggregation, 90-day pruning, DB backup; wired into install-cron.sh at 03:15
  • session/CLAUDE.md wake-up routine v2: consult connectors + journal patterns, log outcomes, re-auth nudge on connector failure

P3 (#115)

  • Telegram bridge (bin/adhdo-telegram, adhdolib/telegram.py, systemd unit): stdlib long-polling, chat-ID allowlist, per-chat rate limit (media counts too), text-only, advisory crisis regex flag, all inbound journaled, injection via bin/wake's escaping contract with framing-marker neutralization (user text can't spoof [event:...]), at-least-once delivery (offset persisted after handling), daemon survives injector/sendMessage failures, token scrubbed from all output
  • Read-only dashboard (bin/adhdo-dashboard, systemd unit): GET/HEAD only, self-contained HTML + /api/state + /api/journal, secrets scrubbed, generic 500s (detail to stderr), boot-race-hardened unit + bounded bind retry

Verification

  • Full suite: 169 passed (60 on main). No stubs/TODOs in new code.
  • On-device steps documented in E2E_CHECKLIST.md: re-run install-cron.sh (heartbeat timer + rollup entry), BotFather token + allowlist + enable adhdo-telegram, enable adhdo-dashboard, plus the pre-existing human-gated P1 items.

Closes #115. Part of #114 (connector setup + hardware nudge tuning remain on-device).

https://claude.ai/code/session_01Tbd7qVjznvPwwYaWmUnWHP

)

- _parse_at: accept ISO-8601 datetime, date-only (midnight), and
  HH:MM[:SS] time-of-day; invalid input now raises ToolError('usage')
  instead of an unhandled ValueError (previously '09:30:00' crashed).
- Add _parse_at tests covering ISO full/space/date-only, HH:MM rollover,
  HH:MM:SS, unpadded hour, and invalid inputs.
- Add quiet-hours boundary tests (exact start, exact end, wrap-around
  midnight, non-wrap window, equal start==end) pinning half-open
  [start, end) semantics; no off-by-one bugs found in nudgelimits.

Claude-Session: https://claude.ai/code/session_01Tbd7qVjznvPwwYaWmUnWHP
…errors, logical device names, jellyfin fallthrough tests (#114)

- cast stop now journals a cast event ({action: stop, device}) like play
- device-not-found is now the same ToolError('no_devices') shape across
  play/stop/volume via shared resolve_device/device_not_found helpers;
  stop no longer silently ignores a missing device
- output and errors report the logical (configured) device name, with
  the real cast name as cast_name; status rows carry both device+name
- volume arg validation returns a usage envelope instead of crashing
- tests: stop journaling, symmetric not-found across subcommands,
  jellyfin fallthrough (unreachable + empty playlist + happy path),
  logical-name reporting

Claude-Session: https://claude.ai/code/session_01Tbd7qVjznvPwwYaWmUnWHP
Separate service from adhdo-httpd: bin/adhdo-dashboard serves a single
self-contained HTML page (inline CSS/JS, 30s auto-refresh) plus JSON APIs
/api/state and /api/journal. GET-only (405 otherwise), output scrubbed via
envelope.scrub, bind/port from config (dashboard.bind falls back to lan_ip,
port 8766). Includes systemd unit, config example, contract tests, and E2E
checklist entry for on-device verification.

Claude-Session: https://claude.ai/code/session_01Tbd7qVjznvPwwYaWmUnWHP
…ome patterns, nightly rollup+prune, wake-up routine v2

- journal outcome <intervention> <worked|partial|ignored|backfired> [feedback]
- journal patterns now surfaces outcome_by_intervention, outcome_by_daypart,
  success_rate_by_intervention
- journal rollup aggregates complete days into rollups and prunes raw
  events/audit rows older than 90 days (idempotent, skips today)
- scripts/adhdo-rollup.sh nightly at 03:15 via install-cron.sh, with
  journal.db backup copy
- session/CLAUDE.md wake-up routine v2: connectors, patterns-before-acting,
  outcome logging, re-auth nudge on connector failure

Claude-Session: https://claude.ai/code/session_01Tbd7qVjznvPwwYaWmUnWHP
- envelope: narrow BaseException to Exception so KeyboardInterrupt and
  SystemExit propagate; drop redundant SystemExit re-raise
- nudge: extract prune_cache() and run it unconditionally on every
  invocation (previously skipped on TTS cache hits)
- heartbeat: replace cron */25 (10-min gap at hour boundaries) with a
  systemd user timer (OnUnitActiveSec=25min) for a true 25-min cadence;
  install-cron.sh installs and enables it
- deploy.sh: install piper-tts idempotently; copy systemd .timer units
- docs: catchup-vs-schedule interplay doc, referenced from E2E_CHECKLIST

Claude-Session: https://claude.ai/code/session_01Tbd7qVjznvPwwYaWmUnWHP
…wake

- adhdolib/telegram.py: stdlib-urllib Bot API client, Bridge with chat-ID
  allowlist, per-chat per-minute rate limit, advisory crisis regex screen
  (ported/tightened from legacy llm_client.py SafetyMonitor), journaling
- bin/adhdo-telegram: run daemon (long-poll, persisted offset, injects via
  bin/wake's literal-send-keys contract) + send subcommand
- token scrubbed from all envelope/error output (sanitize.redact)
- systemd/adhdo-telegram.service, config defaults + example, 28 tests
- E2E_CHECKLIST.md: on-device bot token/allowlist setup steps

Claude-Session: https://claude.ai/code/session_01Tbd7qVjznvPwwYaWmUnWHP
# Conflicts:
#	adhdo2/E2E_CHECKLIST.md
…least-once delivery + daemon resilience + framing-marker neutralization + media-flood rate limit, dashboard generic 500s + proper HEAD + boot-race-hardened unit + real-wiring test, cast stop clears now_playing on missing device, prune_cache race tolerance, doc/test gaps

Claude-Session: https://claude.ai/code/session_01Tbd7qVjznvPwwYaWmUnWHP
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.

📱 P3: Telegram bridge + read-only dashboard

1 participant