Commit Graph

17 Commits

Author SHA1 Message Date
gitea 44b7298a15 Durable result delivery: OUTBOX + idempotent INBOX so results never die
build / build (push) Successful in 46s
CI / compile (push) Successful in 10s
CI / unit (push) Successful in 20s
CI / integration (push) Successful in 27s
An 8h search result must survive a transient bot outage, an api/address
misroute, or a restart of either side. Make the librarian->bot result
path durably at-least-once with idempotent rendering:

Shared: durable_queue.DiskQueue - a dependency-free, atomically-written,
one-file-per-key disk queue (unit-tested), shared by both images
(added to Dockerfile.librarian; the bot already COPYs *.py).

Librarian (sender): finished results go to a persistent OUTBOX before
sending; delivery retries with backoff; an entry is removed only on a
positive ACK; a resender thread keeps flushing the OUTBOX, so a result
survives a bot outage AND a librarian restart (OUTBOX is on the state
volume) - it simply keeps trying until acked.

Bot (receiver): /conjurer is now idempotent and durable - each result is
persisted to an INBOX before acking and only queued if its uuid was not
already delivered (dropped as a duplicate) or already pending. Once the
cog actually renders it, mark_delivered() records the uuid and clears the
inbox, so the librarian's resends become no-ops. On startup the bot
replays any accepted-but-unrendered result from the INBOX, so a bot crash
mid-flight doesn't lose it. Pongs stay ephemeral.

Together: the librarian keeps a result until the bot confirms it; the bot
keeps it until it is on screen; duplicates never double-render. Combined
with the deploy return-path fix, an expensive result no longer vanishes.

Tests: unit test_durable_queue; integration test_librarian_outbox
(retry/backoff, resend survives outage) and test_result_durable_delivery
(persist, dedup pending, dedup delivered, replay, pong not persisted).
Suite: 55 unit + 39 integration green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 15:31:37 +00:00
gitea 5d321f2f5b Librarian: busy-aware ping + per-query lost-result watchdog
CI / compile (pull_request) Successful in 10s
CI / unit (pull_request) Successful in 20s
CI / integration (pull_request) Successful in 20s
CI / compile (push) Successful in 10s
CI / unit (push) Successful in 21s
CI / integration (push) Successful in 20s
build / build (push) Successful in 57s
Two refinements to the librarian health/delivery story, matching how it
actually behaves under load:

1. Busy-aware ping (case b - broken return path). A ping arriving while
   the worker is grinding a search no longer queues behind it (which made
   a healthy-but-busy librarian time out and look dead). The librarian
   tracks worker_busy and, when set, pongs back IMMEDIATELY without
   touching the queue. Being busy is fine - you can keep piling searches
   on. The ping still travels the librarian->bot return path, so it keeps
   catching the one thing it must: a disrupted/incompatible return path
   where queries vanish. Idle pings still go through the internal queue.

2. Per-query watchdog (case a - finished but result lost). The librarian
   now tracks every search uuid's lifecycle (queued -> processing ->
   gone) in active_queries, exposed via a new POST /query_status. After
   dispatching a search the bot records it in self.pending; watch_pending
   polls /query_status for each. While the librarian still knows the uuid
   the search is progressing - left alone. The moment a uuid VANISHES
   there while still pending on the bot, its result was computed but never
   delivered: after a grace window (to rule out an in-flight result) the
   bot posts a notice to the channel - but ONLY then. A normally delivered
   result is popped from self.pending by check_data_q and never flagged.

Hardening: the worker's search body is now wrapped in try/except/finally
so a crashing search can't kill the worker thread (which would freeze the
queue), and worker_busy / active_queries are always cleared. The grace
logic lives in a dependency-free librarian_watchdog.pending_verdict so it
is unit-testable without discord/pdf libs. /ping and /query_status are
plain (sync) views so they run without flask[async].

Tests: unit test_librarian_watchdog (verdict transitions); integration
test_librarian_query_lifecycle (query_status known/unknown + auth,
idle-ping-queues, busy-ping-pongs-directly). Suite: 28 integration + 48
unit green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-01 18:58:03 +02:00
gitea defc482a22 fix: batch A - crash bugs, a leak, and startup/edge fragility
CI / compile (pull_request) Successful in 10s
CI / unit (pull_request) Successful in 19s
CI / integration (pull_request) Successful in 10s
build / build (push) Failing after 46m40s
CI / compile (push) Successful in 20s
CI / unit (push) Successful in 19s
CI / integration (push) Successful in 16s
Seven confirmed defects from the code audit, each small and low-risk.

* ai_functions.get_random_cyclic_message: random.randint(0, len(CYCLIC_WORDS))
  is inclusive -> could return len -> IndexError. Now randrange(len) + guard on
  an empty CYCLIC_WORDS.
* librarian_commands.get_image_sadox: random.randrange(0, len(res)-1) never
  picked the last comic and raised ValueError('empty range') on a single file.
  Now randrange(len) + an empty-dir guard.
* ai_commands image generation: every DALL-E error branch replied but did not
  return, so control fell through to `if response:` with response unbound ->
  UnboundLocalError right after the friendly message. Each branch now returns;
  response is pre-initialised; and PermissionDeniedError no longer passes a
  (message, text) tuple as a single arg.
* search_bot DOI match: `item["DOI"] in data` was a substring test, so a DOI
  that is a prefix of a longer one (10.1/1 vs 10.1/12) produced a false 'exists'
  hit. Now matches the line's first whitespace token exactly, via an O(1) dict
  index built once per consumer (also removes the O(queried-DOIs) per-line scan
  - a real win for large databases).
* communication_subroutine.scan_incoming: matched records were never removed
  from awaiting_q, so it grew unbounded over uptime and a reused UUID could
  re-match a stale record. Matched records are now dropped after dispatch.
* communication_subroutine.id3: (resp.headers.get("icy-name") or "").title()
  guards against a stream that omits headers (was AttributeError on None,
  500-ing the /prepped_tracks "next" handler).
* betoniarka.scan_tracks: waits for the radio logs to exist instead of dying
  with FileNotFoundError on a fresh deploy (which silently killed the
  now-playing forwarder until a restart).

Verified: tests/unit/test_search_bot.py gains exact-match and trailing-metadata
cases; full unit job 43 passed. Remaining observations (image-gen stale
/home/pi fallback paths + dead FileNotFoundError-after-OSError branch; tailer
still vulnerable to mid-run log rotation; DOI-first-token assumption) noted for
follow-up - none are crashes on the normal path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-31 23:01:27 +02:00
gitea b13a8afa01 bot: queued AI query interface + librarian AI review of results
CI / integration (push) Failing after 2m23s
CI / compile (push) Failing after 1h53m43s
CI / unit (push) Failing after 1h53m32s
build / build (push) Failing after 1h54m2s
Two connected features.

1) AI query interface (via the comm layer). communication_subroutine gains an
AI_QUERY_Q, a submit_ai_query() in-process entry point, and an authed
POST /ai_query endpoint ({prompt, channel_id, request_type?, username?}). The
prompt is queued and answered asynchronously by a new tasks.loop worker in the
always-loaded AI cog (Events), which calls handle_response - so it runs on
whichever backend $gadaj_teraz currently selects (GPT or Claude) - and posts the
answer to the requested channel, chunked to Discord's limit. request_type "NONE"
(default) is a clean one-shot: no persona system prompt, no memory write. The
worker starts before the OpenAI guard in cog_load, so it also runs on a
Claude-only box; cog_unload cancels it.

2) Librarian AI review. New command $wyszukaj_z_recenzja mirrors
$wyszukaj_linki_do_dokumentow but sets ai_review=True on the QueryControl, which
rides the round-trip and is matched back by UUID. When the hits return,
check_data_q sends the raw list as before, then - if flagged - hands the same
list (already in Crossref-relevance order) plus the search phrase to the AI
queue for a weighted re-rank and per-source review, delivered to the same
channel. QueryControl gains an ai_review flag (default False, so the orphan path
and all existing callers are unaffected).

Confirmed separately (and noted in the docs): the DOI list the AI receives is
pre-sorted by Crossref relevance - the librarian pipeline only filters (drops
title-less items) and splits (in-db / not-in-db), never re-sorts, and relies on
insertion-ordered dicts (Py 3.7+).

Verified: /ai_query auth (401/200/400/open), submit_ai_query and the queued
dict shape, and the QueryControl flag - via a Flask test client and
tests/integration/test_ai_query_endpoint.py (5 tests, all pass; integration
suite 11 passed, the 3 failures are the pre-existing /clear_pr_pls musician
tests fixed on a separate branch). Full first-party compile clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 09:27:11 +00:00
Michal Tuszowski 5adeb1b384 Land prototype on main (fix stacked-PR retarget gap)
PRs #10 and #11 were merged into their intermediate base branches
(restructure/working-copy-root and proto-improvements) rather than main,
because the stacked PRs' bases were not auto-retargeted (the branches were
not deleted on merge). As a result main only received the #9 restructure
and is still the plain working-copy bot.

This brings the full prototype onto main as a clean delta on top of the
current main tree (identical content to proto-improvements, but with main
ancestry so it merges without the squash-induced rename/delete conflicts):

- constants.py: env-var config, safe JSON loading, dependency guards,
  env->netrc tokens, API_SHARED_KEY + service_headers(), CONAN_* config
- communication_subroutine.py: queue timeout/Empty, daemon threads,
  cooperative stop_event, inbound _authorize_request()
- bot.py: asyncio event loop + load conanjurer_commands
- music_functions / radio_commands / librarian_commands: X-Conjurer-Api-Key
- conanjurer_commands/_functions: fixed + integrated bridge with RCON
  player-join notifications
- requirements_bot.txt: aiomcrcon, asyncssh
- conjurer_musician/.gitignore: keep runtime playlists/mp3 out of the repo

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 11:28:14 +02:00
Michal Tuszowski a64fb2da57 Restructure: promote working_copy to repo root
Make the stable 'working copy' bot the canonical code at the repository
root so the install/deploy scripts operate against it again.

- Move working_copy/* to root (bot entrypoint is bot.py)
- Restore root-level install/ops scripts from c4fa88e (deploy.sh,
  install_main_bot.sh, status_report.*, conjurer.service, etc.)
- Fix deploy.sh: copy bot.py (was thin_client.py) and add the
  conanjurer_* modules; bump command count
- Remove side-by-side variant dirs (backup_old_docker, prototype_one,
  prototype_musician_one, musician_old, working_copy) and docker cruft
- Keep components as subdirs: conjurer_librarian, conjurer_musician,
  spotify_dl, yt_dlp, fonts, utils, docs

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 23:50:55 +02:00
Michal Tuszowski 8e5e4ce530 Backup old. Preparation for forking 2026-06-16 14:09:27 +02:00
gitea f9ad679833 Dockerization + ai review recomendations. 2025-10-29 14:57:43 +01:00
gitea ba80523d6c aaa 2025-08-16 20:19:37 +02:00
gitea af20f6128c Figure out 2025-08-16 20:16:58 +02:00
gitea 92f2992b97 rollback 2025-08-16 19:16:03 +02:00
gitea ab1ebed5f4 upgrade 2025-08-16 19:12:47 +02:00
gitea 97f43bc6a7 Update AI and librarian commands to use 'GENERAL' context" 2025-08-16 16:58:28 +02:00
gitea e4a47d025c safe 2024-11-15 19:59:50 +01:00
gitea 6aec5d05d2 bgfx 2024-11-10 15:38:59 +01:00
gitea 9a0f869839 Main refactoring 2024-11-10 15:03:44 +01:00
gitea c33cfc27ba New stuff incoming 2024-10-03 13:47:02 +02:00