GET /img/tmdb/{size}/{file} ported from MP ImageHelper / fetch_image
(sanitized cache path under DATA_DIR/cache/images, GLOBAL_IMAGE_CACHE
read switch, md5 ETag / 304, day-based cleanup job). Cache module adds
cache.sweep / cache.images_clean jobs and /cache/tmdb, /cache/images
list and clear endpoints. Library list/detail and the search palette
show posters with the neutral placeholder as fallback; metadata settings
get TMDB cache and image cache cards. Runbook docs/runbook/cache.md and
porting notes docs/porting/moviepilot-tmdb-cache.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Migration 0042_cache adds UNLOGGED kv_cache. PgCache mirrors MP
runtime/cache.py (region, per-key TTL, TLRU maxsize eviction, @cached as
cachedCall); only the storage moves from memory to PG. TMDB request
responses (MP TMDb.request @cached) and recognize results (MP TmdbCache:
same key, {id:0} negative cache with EMPTY_RESULT_CACHE_TTL, type-conflict
checks, backfill after manual assignment) live in kv_cache, shared by all
processes. Settings cache.tmdbTtlHours / tmdbMaxSize / emptyResultTtlSec
map to META_CACHE_EXPIRE / CONF.tmdb / EMPTY_RESULT_CACHE_TTL. TMDB
details now also store posterPath / backdropPath.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Migration 0041_rate_limit adds rate_limit_bucket. PgTokenBucket (@umt/db)
refills and takes tokens under SELECT ... FOR UPDATE on the DB clock and
polls with jitter within a wait budget; penalize() carries Retry-After.
Site adapters get createSharedSiteRateLimiter (key site:<id>), shared by
api / worker / worker:agent, with an in-process serial queue in front and
a local fallback when the bucket is unavailable.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A suspended agent.propose_options / approval call is replayed on resume with the
same toolCallId; foldEvents pushed a second item with the same key, which also
inflated the tool call meter. Also use 「」 in the report footnote.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Auto-fix of 602 string literals across 74 files (half-width punctuation next to
Chinese, parenthesised Chinese, quotes); biome reflow; e2/e6 assertions follow
the new full-width summaries. check:copy now runs with --strict in ci.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HEAD failed biome organizeImports on packages/contracts/src/api.ts; types.ts
now matches deno task gen:types on a fully migrated dev database.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- session page shows the agent.plan TodoList (drawer: one-line progress); options and approval
cards sit at their step; composer has a "先看方案再执行" toggle instead of the mode switch
- settings expose option timeout, timeout action and approval validity; quick goals follow glossary
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- agent.plan emits plan_updated; system prompt asks for a plan first and updates while executing
- agent.propose_options registers a decision and suspends the run (waitForFact, no worker slot);
the answer or timeout (recommended/stop) resumes it as the tool result
- console-session runs get inline approvals for plan-bound capabilities: approve replays the call
with the approval, reject fails it with the reason and records it on the decision
- proposeFirst blocks execute capabilities until an option is chosen; E12 covers all paths
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Settings > Agent: MCP server card (endpoint, enable switch, allowed API keys,
live sessions, recent calls, copyable client config without secrets) and the
capability list (scope, risk, idempotency/replay, authorization state, 7-day
calls and failure rate, last call, schema sheet). Agent session tool cards get
a drawer with input/output schema and the redacted input/output of the call.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
POST/GET/DELETE /api/v1/mcp with API Key auth; tools are the capabilities
visible to the key's scope (download__submit naming), every call goes through
registry.invoke (authorize, audit, idempotency key, ledger) with source=mcp.
Resources: settings summary, pending decisions, subscriptions overview;
prompts: triage_pending, acquire_title, diagnose_task. Sessions bound to the
API key, idle reaping, per-key rate limits, enable switch and key allowlist.
Also GET /capabilities/stats (from audit_event) and /capabilities/tools.
Tests connect with the official SDK client over Streamable HTTP.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- contracts/mcp.ts: capability stats/tools, MCP server status/config schemas, mcpToolVisible
- api.ts: GET /capabilities/stats, GET /capabilities/tools, GET /mcp/status, PATCH /mcp/config
- migration 0039: mcp_server_config single row + audit_event capability index
- deno.json: npm:@modelcontextprotocol/[email protected].1
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Account & security: TOTP setup renders the otpauth URL as a QR code
(qr-code-styling) with the secret as a fallback, shows the 8 recovery
codes once after enabling (copy / download), and offers regenerate and
disable (verification code or recovery code) when enabled.
- New Passkey block: add (named, @simplewebauthn/browser), rename, delete;
hidden outside a secure context.
- Login page: "用 Passkey 登录" when a passkey is registered and the
browser supports it; the TOTP step can switch to a recovery code.
- Account menu links to the settings block instead of its own TOTP
dialog, so enabling always shows the recovery codes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- GET /auth/totp/setup returns the pending secret and otpauth URL (created
on first call, reused on refresh) for the console to render as a QR code.
- Enabling TOTP issues 8 one-time recovery codes (argon2id hashes in
admin_recovery_code); login accepts recoveryCode in place of totpCode,
disable accepts either, and recovery_codes regenerates with the password.
- Passkeys via @simplewebauthn/server: register/list/rename/delete under
the console session, public login options/verify that reuse the login
rate limit and session cookies. Challenges are one-time rows in
webauthn_challenge; RP ID and origins come from WEBAUTHN_RP_ID /
WEBAUTHN_ORIGINS, falling back to the request host. Signature counter
regressions are rejected. SessionInfo gains passkeyEnabled.
- --role rotate-master-key re-encrypts the secret table from
SECRET_MASTER_KEY to SECRET_MASTER_KEY_NEW one row per transaction,
skips rows already on the new key (resumable, swap keys to roll back),
logs progress and audits secret.master_key_rotate. Secrets now record
the master key fingerprint in kdf_params.kid.
- Runbook: recovery codes, passkey configuration, rotation steps and
rollback.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
agent.await and agent.spawn{await:true} now return immediately when
children are still running: the session closes, runAgent registers a
wake signal in agent_run.await_wake and waits with waitForFact without
holding an io/agent worker slot. A child that settles (terminal or
waiting) signals the parent's registered fact, so the parent resumes at
once instead of polling; the suspended call replays and the child
report becomes the tool result. agent.spawn is registered replay-safe;
a suspended spawn is recorded as failed in the ledger so the replay
re-runs it against the same child.
Migration 0037 also adds the admin_recovery_code, admin_passkey and
webauthn_challenge tables used by the account work.
E11 asserts the parent is suspended with no run held by any worker
while it waits, and covers spawn{await:true} end to end.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
/browser/providers, /browser/sessions (open/close/list), navigate/snapshot/screenshot/
click/type/scroll/select/extract/download and control acquire/release. Human input
needs an active lease; expiry or captcha moves the session to waiting with a decision;
release forces a fresh snapshot. Downloads are staged under DATA_DIR/tmp, verified by
infohash and submitted through POST /acquisition-plans. Capability count 21 -> 25.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- include/exclude sync to a per-subscription private rule group (rule_group.hard, migration 0036);
evaluation merges chain hard conditions; private groups hidden from GET /rules, soft-deleted with
the subscription and rebuilt on resubscribe
- MP resolution / quality / effect map to presets written onto the subscription's own slots
(default slot template on create); UHD / Atmos go to the private group include
- downloadSavePath mapped to the downloader path and passed as qB savepath; libraryRootId used
for the first organize when the work has no path binding; keywords appended to active search
- POST /subscriptions/{id}/reset releases stale in-flight plans, clears deferrals, re-evaluates
- GET /subscriptions/calendar from episode air dates; refreshMetadata writes TMDB season
episode air dates for tracked seasons
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- glossary: 10 punctuation rules (full-width in Chinese, CJK/Latin spacing,
full-width parens around Chinese, single ellipsis, 「」 quotes, period rules)
- scripts/check-copy-punctuation.mjs: checks string literals, templates and JSX
text (not comments, types, keys, match/compare strings); --fix, --strict,
--by-file; wired into deno task ci as check:copy in warn mode
- --fix applied to web, server and contracts; files other lanes were editing
(subscriptions, sites, browser, agent, auth/account, inventory) left for them
- tests updated for the changed messages
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Page-level scroll areas only; it returns on scroll up, near the top or at the
bottom, and when focused by keyboard.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Groups keep first-seen order (or a custom order), headers show counts and
toggle with aria-expanded; collapsed groups persist per storageKey in
localStorage (read after mount, failures ignored). The trash list groups by
registered directory when items span more than one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- G6: FileRoot.pendingCount/pendingPartial for download roots (same rule as the
unidentified filter, recursive, cached 5 min, invalidated by files.changed)
- G7: FileEntry.parsedHint from parseReleasePath (file + parent dirs); the web
subline uses it and no longer parses names itself
- G8: FileIdentifyCandidate.posterUrl (TMDB poster_path via adapter and
catalog.lookup, local entities from metadata.posterPath); thumbnails in the
identify panel
- S-7 contract: FilesIdentifyResult.remoteTaskId/remotePending,
MediaServerInventory.partial, EventType identify.completed with
IdentifyCompletedPayload; identify panel (useIdentifyLive) and organize wizard
merge remote results by root + path, buffering events that beat the response
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- GET /inventory/media-servers: per-instance Promise.allSettled, per-instance
concurrency cap (2) and timeout (8s); failed/timed-out instances are unknown,
response carries partial=true (field not yet in contracts)
- S-7: POST /files/identify returns local matches plus remoteTaskId/remotePending
and enqueues identifyRemote; the worker runs the TMDB lookups and publishes SSE
identify.completed {taskId,status,items,truncated,excludedBySize,error};
?wait=true keeps the synchronous path, capped at 15s overall
- catalog.lookup / release.search honour the caller's abort signal
- scripts/dev-db-clean.ts: dry-run by default, --yes to drop leftover test
schemas and w32-/w33-/fe4-/qa- prefixed entities; refuses non localhost:55433
- runbook: dev DB cleanup
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Kysely's Migrator checks for its tables via introspection.getTables(),
which reflects every schema and calls has_schema_privilege /
pg_get_serial_sequence per schema. When another test file drops its schema
at the same moment those functions fail with "schema … does not exist" —
the intermittent server PG test failures on the shared dev database.
createMigrator now hands the Migrator a proxy whose introspection only
looks at the migration schema (or current_schema()). A regression test
churns schemas while migrating; it fails on the old migrator.
Also samples EngineRunQuery's comma-separated status/triggeredBy in the
contracts schema test.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- U4: form save failures toast the first problem and scroll/focus the first
invalid field (policy draft, settings cards, resource drawers, subscription
form) via lib/form-errors.
- U5: FormField keeps 8px between a hint and the next label.
- U6: file manager status bar keeps free space left-aligned, clear of the
Agent button.
- U13: move/copy dialog stops "target is the source folder / inside the
source" before requesting a preview (transferTargetProblem + tests).
- U14: account popover uses the solid float material.
- U15/U16: inbox tile hint shortened; TMDB assign dialog uses the shared
Select and labelled fields (type, TMDB ID, note).
- Copy: "TMDB 已保存" spacing, API Key block no longer repeats the section
title, whole-day intervals read "每 7 天", "最高级别:提示",
"延后:已有计划在途", failed records show "未生成目标路径", upstream model
column "默认模型".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- planImport (sourcePaths mode): when a selected source is a video file,
same-directory subtitles / external audio whose stem (language tags
stripped) matches it join the source list, so the organize wizard, which
expands a directory into individual videos, no longer drops them.
Unrelated sidecars stay out; pathGuard applies to the added files.
- Organize records page honours ?ids= (wizard multi-group redirect): shows
just those records via /files/records, refreshes while running, clear chip.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- GET /dashboard: automation.library {entities, movies, series, seasons,
episodes, assets} and automation.slotSatisfiedRate; home media overview
shows the counts instead of placeholders.
- GET /engine/runs: `status` accepts comma-separated values; new
`triggeredBy` filter (comma-separated) evaluated server-side by scanning
batches and paging with the last inspected row's cursor (scan cap 2000).
- Task stream: source filter and the "active" tab (running,pending) are now
one paginated server query.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
OutboxRelay gains a `mirror` option: listed types are claimed even without a
task route and, after the claim transaction commits, published in order to
the realtime bus (publish failures are logged, delivery state unaffected).
The worker mirrors feed.*, decision.*, download.*, organization.* and
subscription.* (feed.due excluded) with their business-id payloads, so the
home page, inbox, task stream and feed tables finally receive them. Feed
tables refresh on feed.fetched / feed.health_changed; the task stream also
listens to feed and decision events.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Conflicts resolved: routes.ts keeps both /console/status (FE-1) and
/console/search (FE-3); smoke list keeps FE-1/FE-4/FE-5 routes plus the
rules tabs, search results and preferences→rules redirects. Top bar mounts
SearchTrigger and CommandPalette next to the FE-1 inbox bell.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
zod v4 records keyed by an enum require every key; the generic sample only
produced {k: "v"}, so the positive example failed after W9-E.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- slot: new manual_verify event (attention -> verifying, with reason); a
successful organize of a download whose slot is in attention goes back
through verifying and lands by verify_passed on the actually imported
assets; the download's organize_blocked decision is resolved with
resolution {key: manual_organize | organize_retry, organizationRecordId} (U1)
- acquisition: plan purpose is upgrade only when the slot already has a
current asset; a movie slot gap is a fill (U7)
- identity: entities created from TMDB details get metadata fields projected
in the same transaction; TMDB aliases deduped by normalized text, site-title
aliases skipped when the text already exists (U9)
- pi: auth-mode test of a custom Base URL no longer marks the upstream ok, and
says to use the minimal completion to probe the endpoint (U2)
- sites: PUT /sites/{id}/credentials replaces same-kind secrets in place and
names new secrets with site id + random suffix (secret.name is unique; was a
500 on update); M-Team user data uses config/definition apiBaseUrl instead
of deriving api.<domain> from the site URL (Invalid URL on IP/local bases)
- copy: setup error says 初始化口令; backup job points at umt backup
- tests: schema expected tables add site_user_data; migration round-trip
timeout 60s; fake secrets honor label as name; vertical/resolve/pi/slot/
site-center tests for the above
- docs: 09-visual-qa W10-pre status table (U3 root cause: outbox events are
never mirrored to SSE)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Overview: kind tabs, five groups with server counts, card/list views,
slot badges, progress (unknown total = "?"), filters in URL, batch actions
- Detail /console/subscriptions/🆔 season tabs, episode x version-target
matrix with cell sheet (manual fill, plan link), drag slot order,
auto-fill switch and fill budget, refresh coverage
- Create/edit dialog (basic/advanced): work picker via global search,
season/range/total, sites, rule groups, include/exclude, templates,
upgrade strategy, downloader/save path; 409 links to existing subscription
Co-Authored-By: Claude Opus 5.5 <[email protected]>