When download.recheck / task.retry was executed but errored (e.g. the
downloader was unreachable), applyRecoveryOutcome only looked at
succeeded actions and recorded outcome=not_attempted with the summary
"没有执行恢复动作", which is false. Record outcome=failed and name the
failed action instead. Needs image rebuild to take effect.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
torrentSource threw plain Error for a missing site or a site without an
adapter definition, so submitDownload retried to max attempts, left the
plan and slot reserved, and sweepReservedPlans re-enqueued it forever
(agent runs stayed waiting on goal-settled). Throw SiteError
(not_found / unsupported) so the plan fails and the slot is released.
Needs image rebuild to take effect.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The search page 订阅 button and the new-subscription wizard threw away
the TMDB id and created a title-only subscription, which stays pending
(metadata_pending) with no seasons, episode totals or coverage matrix
until some release happens to be identified. Call
/files/entity-from-candidate first and subscribe the entity; fall back
to title tracking only if that fails. Picking a TMDB row in the wizard
now creates the library entity even if the dialog is then cancelled
(idempotent per TMDB id).
Found in REAL-1b on the v0.2.2 test stack; needs an image rebuild to
take effect.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
catalog.lookup only returned the zh-CN localized title, so an English
release name (Dune.Part.Two.2024...) was rejected against "沙丘2" and
files/identify came back with zero candidates. Pass originalTitle
through catalog.lookup and use it as an extra title in ranking.
Found in REAL-1b on the v0.2.2 test stack; needs an image rebuild to
take effect.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- compose: camofox service on by default (internal only, volume umt-camofox,
healthcheck, CAMOFOX_REPLICAS=0 to skip); app depends on it with
service_started/required=false and auto-registers camofox-env from
BROWSER_PROVIDER_URL / BROWSER_ACCESS_KEY / BROWSER_ENABLED
- docker/camofox/Dockerfile: builds upstream tag v1.17.0 (389c996); fixes the
upstream Dockerfile (missing postinstall.js before npm ci, sh -c not
forwarding SIGTERM so logins were never persisted on restart)
- adapter fixes from the real container: coordinate click via evaluate +
selector, focus typing with mode=keyboard, password values redacted from
ARIA snapshots/extract, downloads field names, 410 after browser restart
treated as lost tab; navigate reopens a tab in the same session
- verified-capability table keyed by upstream version (1.17.0 all ten);
other versions stay unverified; probe reports auth failures
- settings: browser providers block (create/edit/delete/probe, capability
table); API POST/PATCH/DELETE /browser/providers, POST .../probe
- scripts/browser-verify: umtbr stack + mock NexusPHP run of REQ 4.7.4 items
- docs: runbook browser §1/§7 (real-instance record), deploy §2.1
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- GET /system/health splits processes into current instances (hosts with
a beat in the last 10 min, newest per host+kind, not stopped) and
retiredProcesses[]; 45s stale and level only consider current ones
- heartbeat writer marks stopped_at on graceful exit (migration 0043),
upsert clears it; pure api role now stops its writer on SIGTERM
- scheduled job system.heartbeat_cleanup (maintain, 1h) deletes rows
without a beat for 24h and samples older than 7 days
- status page: per-role alarms ("io worker 45 秒没有心跳"), collapsible
retired instances; glossary/runbook updated
- bump all members to 0.2.2, CHANGELOG [0.2.2]
Co-Authored-By: Claude Opus 5.5 <[email protected]>
shadcn (web devDependency) pulled a second peer copy of
@modelcontextprotocol/[email protected].1 (zod@3), so the server's zod@4 copy
lived in the npm cache as sdk/1.30.1_1. `deno compile --exclude-unused-npm`
embeds that _1 folder but the pruned snapshot resolves it as sdk/1.30.1,
so every SDK import failed at startup with ERR_MODULE_NOT_FOUND.
- drop the shadcn CLI dependency; vendor its tailwind.css (the only
runtime use) as apps/web/app/shadcn-tailwind.css, removing the duplicate
- scripts/check-compiled-binary.sh: run the compiled binary with an empty
env and `--role preflight`; it must load the full module graph and exit 2
on config validation. Wired into `deno task ci` (check:binary) and the
Dockerfile build stage.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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]>