Commit Graph
80 Commits
Author SHA1 Message Date
Vito Sansevero 2dbb21edc2 fix: clamp paging on tag listings and the page-derived recent offset
tags/stats, tags/{tag}/prompts and tags/filter parsed limit/offset with a
bare int(), so limit=999999 was passed straight to SQLite and a huge
offset raised OverflowError (500). They now share parse_page_params
(limit 1..500, offset 0..MAX_PAGE_OFFSET, 400 on non-integers), and
/recent clamps the offset it derives from ?page= the same way.
2026-10-08 18:01:34 -07:00
Vito Sansevero d6cef849a1 fix: validate image file_info before it reaches the generated_images columns
link_image_to_prompt stored size, dimensions and format straight from the
request body, so a string such as '"><img src=x>' ended up in INTEGER
columns and was echoed back to the gallery. Size and dimensions are now
coerced with int() and bounded (size 0..10^12, width/height 0..65535),
format is matched against a whitelist of media formats, and anything
else is stored as NULL while the image still links. Non-dict metadata or
file_info no longer aborts the link.
2026-10-08 18:01:34 -07:00
Vito Sansevero 507c8a8993 test: close every database before unlinking and anchor public paths in image tests
Each owned test that opens a PromptDatabase now calls close_all() before
removing the file, and temp directories holding a database use
ignore_cleanup_errors, so the suite tears down cleanly on Windows. The
image API fixture lays out a fake ComfyUI tree (<tmp>/output) and pins
GalleryConfig.path_anchors to it so responses can be checked for
root-relative paths.
2026-10-08 18:01:34 -07:00
Vito Sansevero f9da6488d6 fix(autotag): contain batch rows, require media extensions, bound the scan
- The batch tagger runs every generated_images row through
  _resolve_allowed_image_path; rows that exist but lie outside the served
  output directories are skipped and counted in skipped_outside_roots on
  progress and complete events, and the engine receives the resolved path.
- _resolve_allowed_image_path requires a media extension (.png .jpg .jpeg
  .webp .gif .bmp .tiff .tif), so autotag/single returns 403 for other files.
- autotag/start holds a module-level lock; a concurrent start gets 409
  {"success": false, "error": "Auto-tagging is already running"}.
- scan_output_dir walks with a 50,000-file and 12-level cap, skips symlinked
  files and thumbnails, returns path through _public_path, relative_path in
  POSIX form, and a truncated flag.
- Autotag handlers report exceptions via the shared _public_error; the
  local copy is removed. Tests close the database before unlinking it.
2026-10-08 18:01:34 -07:00
Vito Sansevero d83383b9e0 fix(lora): confine LoraManager paths to custom_nodes and hide server paths
- is_civitai_url also refuses URLs with userinfo or a non-443 port, so
  https://evil.com\@civitai.com/x and https://civitai.com:8443/x are rejected.
- resolve_lora_manager_path / custom_nodes_directories: lora/enable and
  lora/scan only accept a LoraManager directory that is a direct child of
  ComfyUI's custom_nodes (folder_paths base_path or registered custom_nodes
  folders, else this package's parent) and whose name contains "lora",
  compared via realpath+normcase; relative public paths resolve against the
  API path anchors. Anything else is 400, so find_lora_directories can no
  longer widen the image allow-list with arbitrary trees.
- lora/enable persists through PromptManagerConfig.save_to_file(), which
  honours PROMPT_MANAGER_CONFIG_PATH, instead of a hard-coded config.json.
- detect/status/enable return path and detected_path through _public_path;
  every lora handler reports exceptions via _public_error, and lora/scan
  errors raised after the SSE stream opened become an error event.
- Tests close the database before unlinking it.
2026-10-08 18:01:34 -07:00
Vito Sansevero b792da53ba fix: read thumbnail generation progress over POST with SseStream
images/generate-thumbnails/progress is becoming POST-only, which the
browser EventSource cannot request. Both thumbnail progress consumers in
the gallery now use SseStream.connect(url, {method: 'POST'}) exactly as
the autotag streams do. SseStream gains addEventListener/removeEventListener
for named event: frames (tested) so the status, start, progress,
file_error, complete and error listeners stay as they were, and an onclose
guard rejects when the stream ends without a terminal frame.
2026-10-08 18:01:34 -07:00
Vito Sansevero 4ab0214f09 fix: address gallery auto-tag review images by path, not digest id
The gallery review flow builds its list from /prompt_manager/images/output,
whose id is a 16-hex path digest, but posted it as image_id to
autotag/single, so the server looked up a database row and every request
404ed. autotagSingleBody (pure, tested) now sends path for output-folder
entries and keeps image_id for database rows, which the admin review flow
(/images/all) still uses.
2026-10-08 18:01:34 -07:00
Vito Sansevero e709a158e6 fix: escape image dimensions and workflow JSON before rendering
openGalleryModal interpolated width, height and file_size (stored from a
POST body, so possibly strings) straight into data-caption and the card
text. showWorkflowData wrote JSON.stringify(workflow) raw into a blank
same-origin window, so a workflow containing </pre><script> ran. Both now
go through ImageHelpers (new pure module, node:test covered); a string
file_size no longer reaches formatFileSize's toFixed either, and a blocked
popup shows a notice instead of throwing.
2026-10-08 18:01:34 -07:00
Vito Sansevero d7be0df380 style: wrap long lines in tests
Split long docstrings and SQL literals (values unchanged) and drop the
unused os import from test_path_security.
2026-10-08 18:01:34 -07:00
Vito Sansevero 1f422d2c43 fix: restore through SQLite's backup API, close_all for shared files, safer output scans
restore_from_file copied the uploaded file over the live database and deleted
its WAL while other PromptModel instances (nodes, image monitor, queue hook)
still held connections, which corrupts on POSIX and fails on Windows. It now
copies pages through the online backup API into the live connection so every
open handle sees the new content. PromptDatabase.close_all() closes the
connections of every thread, which tests need before deleting a database.
Output scans skip symlinked files, filesystem entries get a stable digest id
instead of a per-process hash(), and thumbnail streaming stops when the
client disconnects.
2026-10-08 18:01:34 -07:00
Vito Sansevero 0393d01746 fix: page image listings in SQL and drop three unused helpers
get_recent_images and search_images_by_prompt take limit/offset so the recent
and search image routes no longer over-fetch a 5000-row window and slice it.
generate_content_hash, PromptModel.migrate_database and find_duplicates had no
callers outside their own tests. The isolated logger tests re-apply their
thread filter after update_config rebuilds the handlers. Long lines in the
touched database and image modules are wrapped so the full flake8 hook passes.
2026-10-08 18:01:34 -07:00
Vito Sansevero 11aea013ca test: cover frontend modules
browser-globals.test.js loads every page module through vm with a fake
window, the way the pages do, and asserts the global each one exposes
(HtmlEscape/escapeHtml, DataActions, ApiClient, ListState, SseStream,
PngMetadata, ViewRouter, PromptListSort, ComfyMetadata). sse-stream gains
cases for a POST body, missing handlers and close() before or during
delivery. Every pure module is above 85% line coverage under
node --test --experimental-test-coverage.
2026-10-08 18:01:34 -07:00
Vito Sansevero 247abc3475 fix: tags deep link renders and admin modals close
admin.html loaded tags-page.js after admin.js, so opening the page at #/tags
found no TagsPageManager and rendered nothing; the script order is now
theme-bridge, html-escape, api-client, png-metadata, (list-state, sse-stream,
data-actions), viewer, comfy-metadata, prompt-list-sort, view-router,
tags-page, admin. Route resolution lives in the pure ViewRouter.resolveView().

The auto-tag modal's close buttons called a bare hideModal(), which only
gallery.js defines; they call window.admin.hideModal(). PromptAdmin is
constructed from startAdmin() on DOMContentLoaded (or immediately when the
document is already parsed) instead of at script evaluation time.
2026-10-08 18:01:34 -07:00
Vito Sansevero de194af8bc refactor: one PNG metadata parser
admin.js, gallery.js and metadata.html each carried a copy of the PNG chunk
walker that reads ComfyUI's tEXt/iTXt entries (admin.js had two until the
shadowed one was removed). web/js/png-metadata.js is now the only parser:
it checks the signature, bounds-checks every chunk so a truncated file or a
bogus length cannot throw, decodes tEXt as Latin-1 and iTXt as UTF-8, skips
compressed zTXt/iTXt payloads instead of decoding them as garbage, and stops
at IEND. The three page-level parsePNGMetadata functions delegate to it.
2026-10-08 18:01:34 -07:00
Vito Sansevero 7b6b7ecf3d fix: stale responses, double submits and leaked listeners
web/js/api-client.js wraps fetch with one failure path (ApiError for non-2xx,
success:false and non-JSON bodies) and latestOnly(), which aborts the previous
in-flight request and resolves superseded calls to STALE. The admin search and
recent-page loads share one guard, the gallery page load has its own, so fast
typing or clicking can no longer paint an older result over a newer one.

In-place prompt editing used a blur listener with {once:true} plus a keydown
listener added on every edit, which sent the PUT twice and stacked handlers.
The list now has one keydown/focusout pair bound at construction and
finishEdit() clears the edit state before touching the element, so a save is
sent exactly once. Mutations re-fetch the page currently shown (refreshList)
instead of calling search(), and delete, bulk delete, edit, restore and scan no
longer reload the whole page.

Pagination state is committed only after a successful fetch via the pure
ListState.nextPageState(current, response) in both admin and gallery, so a
failed or aborted page request leaves the current page intact. The gallery
lightbox removed its keydown handlers on a non-existent 'remove' event; a
MutationObserver now drops them when the modal leaves the document.

web/js/sse-stream.js reads server-sent events over fetch so POST-only progress
endpoints can stream.
2026-10-08 18:01:34 -07:00
Vito Sansevero edc0ee3d00 fix: HTML escaping covers attribute context
escapeHtml now escapes & < > " ' (shared web/js/html-escape.js, loaded by
admin, gallery and metadata pages) instead of the textContent/innerHTML
round-trip that left quotes intact, so a tag, category, checkpoint or file
name containing a quote can no longer break out of a data-*, value or title
attribute.

Inline onclick handlers that interpolated server data now carry data-action
and data-* attributes and are dispatched through one delegated listener per
page (web/js/data-actions.js); the img onerror fallback uses
data-fallback-src with a capturing error listener. Every remaining template
value written into an attribute or text node (prompts, checkpoints, file
paths, log lines, directory names, categories, suggestions) is escaped.

metadata.html no longer builds an "explorer /select" shell command from the
file name (Electron-only branch); the path is copied to the clipboard.
2026-10-08 18:01:34 -07:00
Vito Sansevero f99e76a135 fix: truncating logs no longer rolls the main log over or pads it with zeros
truncate_logs emptied prompt_manager.log through a second open(), leaving the
rotating handler's stream at its old offset: the next record was written past
the new end (zero padding on POSIX) and tell() still reported the old size, so
the file rolled over and resurrected the backup that had just been deleted.
It also logged once per file from inside the deletion loop, which could do the
same with a small size limit. The main log is now truncated through the
handler's own stream and a single summary line is logged afterwards.
2026-10-08 18:01:34 -07:00
Vito Sansevero 9d6eb728fc test: make admin, logging and diagnostics tests independent of test order
The diagnostics message no longer carries the absolute path, the restore test
closes its raw sqlite connections before the live file is replaced, the logs
tests declare their own folder_paths base directory, and the isolated logger
filters every handler to the test thread.
2026-10-08 18:01:34 -07:00
Vito Sansevero 78a12b67fd test: cover admin and core API routes
Route tests for stats, cleanup, duplicate scan/delete, settings,
diagnostics, test-link, maintenance (every operation, success and
failure), the output scan internals, and contract-level tests for
backup/restore (status codes and envelope only).
2026-10-08 18:01:34 -07:00
Vito Sansevero cdd5fd6b5e test: cover core API module and gzip middleware 2026-10-08 18:01:34 -07:00
Vito Sansevero e282f6ea50 fix: clamp the logs limit and reject non-integers
GET /prompt_manager/logs accepted any limit up to 1000 and answered 500
when the value was not an integer. The limit is now clamped to [1, 500]
and a non-integer value is a 400.
2026-10-08 18:01:34 -07:00
Vito Sansevero 879247f1ef refactor: settings reads the image monitor through its module
get_settings located the ImageMonitor singleton by scanning sys.modules
for any module with 'image_monitor' in its name, a workaround for the
package being importable under two identities. It now uses the same
try-package / except-top-level import as the rest of the API
(_current_image_monitor) and falls back to the configured roots when no
monitor is running.
2026-10-08 18:01:34 -07:00
Vito Sansevero 38f4c0af43 perf: run duplicate and output scans off the event loop
The duplicate scan walked the output tree and hashed every file on the
aiohttp event loop, and the duplicate delete removed files inline; the
output scan ran its per-file database work one executor hop at a time.

- _find_duplicate_images_sync / _describe_media_file do the walk and
  hashing; find_duplicate_images() runs them via _run_in_executor.
- _delete_duplicate_images_sync / _delete_one_duplicate do the removal
  (absolute or output-relative paths) off the loop.
- The output scan is split into _collect_output_media_sync,
  _extract_batch_metadata_sync and _ingest_scan_batch_sync (one
  executor call per batch for all DB work) with a small SSE generator.
- Media collection is shared and de-duplicates by normcase'd path.
2026-10-08 18:01:34 -07:00
Vito Sansevero b0e60ec4b8 fix: no absolute server paths in API responses
Settings, diagnostics, duplicate/output scans and the logging endpoints
returned absolute filesystem paths, and several error envelopes echoed
str(e) which for OSError carries the full path.

- _public_path() renders a path relative to the ComfyUI base directory
  (folder_paths.base_path, else the output dir's parent) or to the
  parent of an opted-in extra gallery root, falling back to the
  basename; _public_error() reduces OSError to strerror + basename.
- GET /settings returns gallery roots and monitored directories in that
  form; POST /settings resolves relative roots against the same anchors
  (GalleryConfig.resolve_gallery_root) so the GET output round-trips.
- Diagnostics output_dirs, monitored_directories and the missing-DB
  message no longer carry absolute paths; the image monitor is reached
  through the package-or-top-level import fallback instead of a
  three-dot relative import that failed outside the package.
- Duplicate scan 'path' is now relative to the output root and the
  delete endpoint accepts relative paths.
- /logs/files 'path' and /logs/stats 'log_directory' use the public
  form; unexpected errors answer with a generic message.
2026-10-08 18:01:34 -07:00
Vito Sansevero e7fa993a42 fix: log config accepts only known keys
POST /prompt_manager/logs/config forwarded the whole request body into
PromptManagerLogger.update_config(), so any key could be injected into
the logging configuration. The handler now accepts only LOG_CONFIG_KEYS
(level, max_file_size, backup_count, console_logging, file_logging,
buffer_size), type-checks each value (level name, non-negative int,
bool) and answers 400 listing unknown keys. Malformed or non-object
bodies are 400 instead of 500.
2026-10-08 18:01:34 -07:00
Vito Sansevero c855a1e23b fix: gallery roots are restricted to ComfyUI directories
A gallery root could be set to '/' (or 'C:\') through POST
/prompt_manager/settings because the deny list only matched a few
system prefixes. The root was persisted to config.json and fed into the
image-serving containment check, which then allowed any file on disk to
be read through /prompt_manager/images/serve/.

- GalleryConfig.validate_gallery_root() accepts a directory only when its
  realpath lies inside a ComfyUI directory (folder_paths output/input/
  temp/user) or a directory listed in PROMPT_MANAGER_EXTRA_GALLERY_ROOTS;
  filesystem roots, the home directory, files and missing paths are
  rejected.
- save_settings returns 400 and persists nothing when any root fails.
- _get_all_output_dirs() filters configured roots through the validator
  and never falls back to a rejected root.
- PROMPT_MANAGER_CONFIG_PATH relocates config.json; load/save default
  to it.
2026-10-08 18:01:34 -07:00
Vito Sansevero 5110ff1aff test: isolate the clock and log buffer from background threads of other tests 2026-10-08 18:01:34 -07:00
Vito Sansevero 3a67c4b952 test: cover database operations
Behaviour tests for export (json/csv/errors), duplicate detection and
merging, image metadata storage and lookup, missing-file cleanup,
statistics, bulk operations, orphan pruning, consistency checks,
subfolder listing, tag queries and management edge cases, legacy tag
column parsing, config-driven path resolution, model housekeeping and
the TEXT -> INTEGER prompt_id migration. database/operations.py and
database/models.py are both above 85% statement+branch coverage.
2026-10-08 18:01:34 -07:00
Vito Sansevero ae11e81203 fix: image uniqueness by full path, escaped LIKE search, no usage stamp on save
generated_images was UNIQUE(prompt_id, filename) on the basename, so
ComfyUI's same-named files in different date folders were silently
ignored by INSERT OR IGNORE, and the lastrowid == 0 branch after it
never ran. The table now carries file_path (normcase/normpath/abspath
of the image path) with UNIQUE(prompt_id, file_path); the migration
collapses rows that normalise to one file and supersedes the old
basename migration, which would otherwise undo it on every start.

search_prompts and the tag search escaped nothing, so 100% matched
'100 percent' and a_b matched aXb; user text now goes through
escape_like with ESCAPE. save_prompt no longer stamps last_used_at, the
usage backfill runs only on the upgrade that adds the columns, and the
Recently Used / Most Used orders put never-run prompts (NULL) after run
ones instead of first.
2026-10-08 18:01:34 -07:00
Vito Sansevero 9234dac211 fix: migrations are idempotent and schema init runs once per process
Every PromptDatabase() (nodes, image monitor, API, diagnostics) re-ran
table creation and all migrations. Schema init is now cached per
normalised path (class-level set under a lock) and re-run only when the
file disappeared, after a restore, or after reset_schema_cache().

The copy-and-swap migrations drop a stale *_new table left by an
interrupted run, execute inside an explicit transaction with
PRAGMA foreign_keys = OFF (DROP TABLE prompts used to cascade-delete
every generated_images row), roll back cleanly and log and re-raise
sqlite3.Error instead of swallowing it.
2026-10-08 18:01:34 -07:00
Vito Sansevero b5720904cd fix: backups include WAL data and restores are integrity-checked
The database runs in WAL mode, so a shutil.copy2 of prompts.db silently
dropped every committed row still in prompts.db-wal; the backup
download and the pre-restore safety copy both did that. Restore also
overwrote the live file with connections open, read the whole upload
into memory before checking the 100 MB cap and never ran
PRAGMA integrity_check.

PromptModel.backup_database now uses the SQLite online backup API,
verify_database_file opens a candidate read-only and checks header,
integrity and the prompts table, and restore_from_file backs up the
live database, closes every thread's connection, copies the file,
removes stale -wal/-shm and re-runs migrations. The admin handlers
stream the upload to a temp file with an early size cutoff, reject
anything verify_database_file refuses with a 400 and keep the
{success: ...} JSON shape. The schema-init connection is now closed
instead of leaking.
2026-10-08 18:01:34 -07:00
Vito Sansevero 43d2352fff fix: saving an existing prompt concurrently returns its id
Node execution, the queue hook and the image monitor all do
get_prompt_by_hash() then save_prompt(). When two of them race on the
same text the second INSERT failed on UNIQUE(hash) and surfaced an
IntegrityError. save_prompt now catches it, re-fetches by hash and
returns the existing id, so eight threads saving one prompt end with a
single row and every record_prompt_use counted.
2026-10-08 18:01:34 -07:00
Vito Sansevero 1a957b8fb3 fix: give every thread its own sqlite connection
PromptModel.get_connection() handed every thread the same
check_same_thread=False connection, so a commit or rollback from the
watchdog, executor or queue-hook thread could land on another thread's
half-done write (sqlite3 raised 'bad parameter or other API misuse'
under concurrent saves). Connections now live in threading.local, each
with WAL, Row factory, foreign_keys and busy_timeout, and are tracked so
they can be closed. PromptModel.close() / PromptDatabase.close() close
the calling thread's connection so tests and restores can delete the
file on Windows.
2026-10-08 18:01:34 -07:00
Vito Sansevero 0ab510f9a5 docs: point at the moved canvas extension under web/comfy 2026-10-08 18:01:34 -07:00
Vito Sansevero 0ff59b5f5b test: cover __init__
Version parsing from pyproject (and the unknown fallback), and a server
whose add_on_prompt_handler raises: nodes and monitoring still come up.
2026-10-08 18:01:34 -07:00
Vito Sansevero 3e1e15a629 test: cover utils/comfyui_integration
Singleton re-initialisation keeps registrations, latest prompt per thread,
cross-thread fallback with the five-minute recency limit, global lookup for
the current thread, and cleanup of old registrations.
2026-10-08 18:01:34 -07:00
Vito Sansevero 580063cf5c test: cover utils/logging_config
Isolated logger manager on a temporary log directory: memory buffer order,
level filter and 1000-entry cap, rotation and backup listing, tail and whole
file reads, path traversal rejection, truncation (including removal
errors), level changes across prompt_manager loggers, handler rebuilds.
2026-10-08 18:01:34 -07:00
Vito Sansevero c001f94213 fix: diagnostics dependency check survives a watchdog without __version__
check_dependencies read watchdog.__version__ for its log line; recent
watchdog releases have no such attribute, so the AttributeError escaped and
took the whole diagnostics run down. Read it with a default.

Also covers utils/diagnostics.py: database and images-table checks against
a real temporary database, file system check, ComfyUI output detection
(folder_paths, configured directories, relative fallback, nothing found),
dependencies, the full run and the test image link.
2026-10-08 18:01:34 -07:00
Vito Sansevero ee80a344e2 test: cover prompt_manager_base, prompt_manager and prompt_search_list
Node base: init wiring, metadata update on re-save, run counting, database
error paths, tag parsing, search and recent API wrappers, gallery start
failure, LoRA trigger-word injection (disabled, missing modules, enabled).
PromptManager: missing CLIP error, encoding of the combined text with a fake
CLIP, tracking failure tolerance, empty text. PromptManagerText: tracking
failure tolerance. PromptSearchList: filters, multipart and LoRA-only
skipping, newline collapsing, preview, empty and error results.
2026-10-08 18:01:34 -07:00
Vito Sansevero 205cf153ea fix: image monitor recent-prompt fallback reads the paginated result
get_recent_prompts returns {"prompts": [...]} but _get_fallback_prompt
indexed it like a list, so the last-resort link to the most recent prompt
always failed silently. Accept both shapes.
2026-10-08 18:01:34 -07:00
Vito Sansevero 3df88df583 fix: stop ComfyUI from loading the admin bundle into the canvas page
WEB_DIRECTORY = "web" made ComfyUI inject every .js under web/ (admin.js,
gallery.js, vendored Tailwind and ViewerJS) into the canvas page. The node
extension moves to web/comfy/prompt_manager.js (content unchanged; its
../../scripts/app.js import resolves against the served URL, which is the
same) and WEB_DIRECTORY points at that folder only. Admin and gallery assets
are still served from web/ by the extension's own routes.

tests/test_sort_contract.py still reads web/prompt_manager.js and must be
updated to the new path by its owner.
2026-10-08 18:01:34 -07:00
Vito Sansevero 7d65ee5516 refactor: one prompt hash implementation
utils.hashing.generate_prompt_hash is now the only prompt hash:
PromptManagerBase._generate_hash delegates to it and the inline
hashlib.sha256 copies in the prompt tracker and image monitor are gone.

Remove PromptManagerBase._open_web_interface (it targeted a web_interface
folder that never existed) and the unused restart_gallery.py script.

IS_CHANGED on PromptManager and PromptManagerText now includes category and
tags, so editing either re-runs the node and updates the saved metadata.
2026-10-08 18:01:34 -07:00
Vito Sansevero 59ad23201b fix: image monitor validates directories before creating the observer
start_monitoring assigned self.observer before checking that any directory
existed, so with no valid directory the monitor was stuck "already running"
and stop_monitoring raised on join of a never-started observer. Directories
are validated first and only a started observer is kept; stop_monitoring is
safe to call at any time and always joins. New is_monitoring property.

The per-image threading.Timer is replaced by one daemon worker: paths are
deduplicated and bounded (MAX_PENDING_IMAGES), processed in creation order
(batch images pop the prompt queue in order) after the processing delay and
once the file size is unchanged across two reads.
2026-10-08 18:01:34 -07:00
Vito Sansevero 5464e3295c fix: keep batch image queue aligned when unrelated workflows save images
The metadata strategy returned None for images whose embedded workflow has
no positive PromptManager node, so the handler popped the batch FIFO for
them and every later batch image linked to the wrong prompt. It now returns
a tri-state: linked (prompt found), foreign (no PromptManager prompt in the
workflow, so the image is skipped and no fallback applies) or unknown (only
then the queue, snapshot, tracker and recent-prompt fallbacks run).
2026-10-08 18:01:34 -07:00
Vito Sansevero 729190309c fix: count prompt runs when ComfyUI dequeues the prompt, not when it is submitted
The on-prompt hook runs before ComfyUI validates the request, so counting
there bumped run_count for prompts that were rejected or deleted from the
queue. The hook now only resolves the positive PromptManager texts, mints a
UUID4 prompt_id when the client sent none, and stashes the hashes under that
id (QueuedRuns, bounded to 1000 entries). PromptQueue.get is wrapped
idempotently; when a prompt is dequeued for execution its stashed runs are
counted (or remembered as pending first use). Servers without a prompt_queue
fall back to counting at submit time with one warning.

__init__ defines _global_db before the monitoring block and passes a factory
that raises RuntimeError while the database is unavailable; the package is
now imported under test with server and folder_paths stubbed.
2026-10-08 18:01:34 -07:00
Vito Sansevero 0a46420385 fix: do not follow negative text inputs when tracing the positive prompt
_TEXT_KEY matched input names such as text_negative, so a styler node feeding
a sampler's positive input pulled its negative PromptManager node into the
positive trace. Input names matching (^|_)neg(ative)?($|_) are now skipped
(new follows_input helper).
2026-10-08 18:01:34 -07:00
Vito Sansevero 248d5880e6 fix: never treat negative-only PromptManager nodes as the run prompt
run_prompt_nodes fell back to every PromptManager node whenever no positive
node was traced, including graphs that do have a sampler and whose only
PromptManager node feeds its negative input. Fall back only when the graph
has no recognisable sampler (new has_sampler helper). Samplers named like
one are recognised even without a model link. Adversarial graphs (cycles,
10k-node fan-out, non-dict inputs) are covered.
2026-10-08 18:01:34 -07:00
Vito Sansevero 5c592e4a87 fix: saving LoRA settings without a key keeps the stored CivitAI key 2026-10-08 18:01:34 -07:00
Vito Sansevero 114f8c779d test: cover autotag and lora integration 2026-10-08 18:01:34 -07:00
Vito Sansevero a6d4facfbf refactor: autotag model loading is injectable 2026-10-08 18:01:34 -07:00