diff --git a/AUDIT.md b/AUDIT.md index a6464ec..804b69a 100644 --- a/AUDIT.md +++ b/AUDIT.md @@ -9,8 +9,7 @@ released agent, or reject a valid completion. ## Evidence -- `go build ./...`, `go vet ./...`, `go test ./...`: pass. - `go test -race ./...`: fails in orchestrator monitor tests. +- `go build ./...`, `go vet ./...`, `go test ./...`, and `go test -race ./...`: pass. - Live B17: release `seq=251`, re-lease `252`, completion `262`; the simple probe needed six approvals, logged a `409 lease version conflict`, and recorded `consumed:0`. @@ -25,8 +24,8 @@ released agent, or reject a valid completion. | H1 | **Closed 2026-07-30.** The checkout-owning worker and coordinator turn path now use `RotationStateMachine`. Workers persist harness-native identity (Claude/Codex transcript, OpenCode SQLite session id), apply soft/milestone/thrash/hard-boundary decisions, and record unknown activity/occupancy/boundary as degraded health rather than zero usage. | Verified by `go test -race ./...`; the existing turn-policy coverage now exercises the shared state machine. | | H2 | **Closed 2026-07-30.** `PrepareRelease` verifies immutable `TASK.md`, checkpoints all repository work except protocol markers, always pushes the per-task project's scratch anchor, verifies it with `ls-remote`, and only then seals the CAS handoff. | `TestScratchCommitCapturesAllGitStatesExceptProtocolMarkers` covers staged, deleted, renamed, untracked, and protocol-marker cases; release uses the configured project remote. | | H3 | **Closed 2026-07-30.** Worker state persists idempotent release transactions through `prepared → anchor_pushed → event_committed → pickup_validated → predecessor_retired`. Release/pickup endpoints bind transaction, anchor, and lease version; a predecessor remains mapped and is retired only after matching pickup validation. | `TestReleaseTransactionSurvivesReLeaseUntilMatchingPickup` covers transaction propagation and pickup epoch binding; full race suite passes. | -| H4 | Lease loss can create split-brain work. Worker-offline releases after heartbeat TTL, while the old worker drops release/block mappings without stopping the pane (`cmd/orchestra/main.go:384`; `cmd/orchestra-worker/main.go:592`). Worker health may say herdr is unreachable but routing checks only heartbeat. | Give every lease a durable epoch/fencing token. Accept renew/release/complete only from that owner+epoch. Reassign only after explicit relinquish or lease expiry. On ownership loss, quarantine/stop the old pane before forgetting it. Admit workers only with fresh local-herdr health. | -| H5 | The event log can diverge from memory: `Store.Append` mutates the projection before the event write/fsync (`internal/store/store.go:328-345`). Legal lifecycle transitions are not enforced; the legacy completion endpoint is not lease-owner fenced (`cmd/orchestra/main.go:134-185`). | Validate transition+owner+epoch, append/fsync first, then project. Recover projections only from the log. Make CAS/state writes temp+fsync+rename and fail closed on corrupt worker state. Remove or fence the legacy harness endpoint. | +| H4 | **Closed 2026-07-30.** Every new lease carries an opaque durable `lease_epoch`; renew/release/pickup/complete validate the exact harness owner and epoch at the store boundary and federation API. Offline heartbeats retain leases until expiry, new workers require a fresh reachable local-herdr probe, and local/worker ownership loss stops or durably quarantines the old pane before its mapping is dropped. | `TestLeaseEpochFencesStaleOwnerLifecycleWrites`, `TestAvailableRequiresFreshReachableLocalHerdrHealth`, plus the full race suite cover stale re-lease/completion and health admission. | +| H5 | **Closed 2026-07-30.** `Store.Append` validates legal state/owner/epoch transitions, fsyncs the event before applying its projection, and replays projections solely from `events.jsonl` (snapshots are disposable caches). CAS, worker/federation/coordinator state use temp-file + fsync + rename; corrupt worker state aborts startup. The live legacy `/v1/harness/complete` route is retired (410); its retained compatibility handler is fenced if invoked directly. | `TestOpenRebuildsOnlyFromLogAndIgnoresCorruptSnapshot`, `TestWorkerRefusesCorruptDurableState`, and `go test -race ./...` pass. | ## P1 — autonomy and recovery diff --git a/TOKEN_MINIMAL_WORKFLOW_PLAN.md b/TOKEN_MINIMAL_WORKFLOW_PLAN.md new file mode 100644 index 0000000..aa8478f --- /dev/null +++ b/TOKEN_MINIMAL_WORKFLOW_PLAN.md @@ -0,0 +1,534 @@ +# Orchestra — Token-Minimal Unattended Workflow Plan + +**Status:** proposed +**Written:** 2026-07-29 +**Objective:** deliver the intended unattended workflow while spending model +tokens only on useful implementation or review work. Routing, supervision, +continuity, Git delivery, reporting, quota accounting, and operator summaries +must be deterministic. + +This plan is forward-looking. `AUDIT.md` remains the defect record, but claims +in either document must be verified against the live call path and a real +worker-owned checkout. + +--- + +## 1. Target workflow + +```text +Vikunja task + → deterministic ingest and eligibility mapping + → router selects a project-compatible, reachable worker/harness + → worker synchronizes the correct project checkout + → worker writes and commits immutable TASK.md + → harness reads TASK.md and performs the task + → worker supervises lease, approvals, context, and liveness + → deterministic quality gate + → worker commits and pushes the result + → optional review stage + → transactional TaskCompleted + → Vikunja reflection and morning brief +``` + +The operator is interrupted only for: + +- a real harness approval that cannot be safely pre-authorized; +- a blocker requiring a human decision; +- failed deterministic gates after the configured retry policy; +- exhausted or uncertain quota; +- an integrity failure involving the checkout, handoff, or pushed anchor. + +### Token boundary + +Model tokens are permitted for: + +- implementing the task; +- reasoning about a blocker; +- an explicitly requested independent review; +- a small semantic handoff only when deterministic state is insufficient. + +Model tokens are not permitted for: + +- task routing or prioritization; +- worker/project selection; +- lease renewal; +- Git inspection, commits, pushes, or cleanup; +- ordinary threshold handoffs; +- completion reports and receipts; +- quality-gate execution; +- quota aggregation; +- standups, briefs, or task-source reflection. + +--- + +## 2. Current baseline + +As observed on 2026-07-29: + +- Source `1ca9d64` passes `go test ./...`, `go build ./...`, and + `go vet ./...`. +- The coordinator reports 28 historical tasks: 22 blocked, 5 completed, and + 1 failed, with no queued or leased work. +- One `workpc-opencode` worker is online, but its installed binary was built + from `95a96d8` with a modified worktree, not current source. +- The worker is fixed to `/tmp/test-e2e`; it is not safe for arbitrary + workpc-affine projects. +- Three tasks recorded as completed still have live idle OpenCode agents. +- Their worker session mappings are gone, so Orchestra cannot manage those + panes through the normal lifecycle. +- Completed worktrees retain `.orchestra-report.md`; two contain uncommitted + task output, and their task branches were not pushed. +- Federated completion receipts record `consumed: 0`. +- Worker leases expire after 30 minutes and have no renewal path. +- Canonical handoff semantic fields are validated and stored, but successors + are told only to inspect `TASK.md` and Git history; the semantic fields are + not consumed. +- Automatic Vikunja ingestion and guarded reflection are not implemented. + +**Operational conclusion:** do not run a valuable real task until Milestones +1–3 are proven. Do not close the existing live panes during implementation or +investigation without explicit operator approval. + +--- + +## 3. Design principles + +### 3.1 One authoritative instruction + +`TASK.md` is the only task instruction supplied to a harness. It contains: + +- immutable task identity and source; +- title and complete description; +- checkable acceptance criteria; +- project-specific quality-gate command or reference; +- the minimal completion signal; +- the prohibition against editing `TASK.md`. + +The initial launch prompt should be bounded and stable: + +> Read TASK.md at the worktree root and execute it. + +Do not inject the title and description again. A pickup prompt may add only +the bounded continuity facts that are not already derivable from `TASK.md` +and the checkout. + +### 3.2 Completion is a transaction + +A task is not complete merely because a report marker exists. The worker +must persist and advance these idempotent phases: + +```text +completion_requested + → gate_passed + → result_committed + → result_pushed + → report_uploaded + → completion_appended + → pane_closed + → worktree_cleaned +``` + +Each phase records its evidence before advancing. Restart resumes the first +unfinished phase. Repeating a completed phase must be safe. + +`TaskCompleted` is emitted only after the pushed anchor is verified against +the configured remote. + +### 3.3 The plane authors mechanical evidence + +Replace the prose `.orchestra-report.md` contract with a zero- or +near-zero-content completion signal such as `.orchestra/done`. The worker +generates the canonical completion report from: + +- task ID, project, worker, harness, and pane; +- base SHA, result SHA, branch, and remote; +- clean/dirty state and diffstat; +- quality-gate commands, exit codes, and timestamps; +- artifact hashes; +- native usage receipts for every lease interval; +- rotation count; +- cleanup outcome. + +The agent does not spend a turn narrating information the worker can prove. + +### 3.4 Leases describe ownership, not wall-clock task duration + +Workers renew leases while they can prove all of the following: + +- the worker is authenticated and healthy; +- the exact task/session mapping is still durable; +- the expected pane exists; +- the expected harness agent remains attached; +- the task version and lease owner still match. + +The coordinator accepts renewal only from the current lease owner using a +version guard. Renewal extends the deadline without creating a new attempt. + +Expiry or worker-offline handling must: + +1. append one observable release/failure event; +2. route that event through the normal retry accounting path; +3. command the owning worker to close the exact pane when reachable; +4. retain last-pane evidence even when cleanup cannot be confirmed; +5. prevent a replacement session from starting until ownership is resolved + or explicitly fenced. + +### 3.5 Workers resolve projects locally + +A worker must not have one global repository for every task it can lease. +Machine-local configuration resolves: + +```text +(machine, task.project) + → local base checkout + → local worktree root + → Git remote + → quality-gate profile +``` + +The worker rejects a lease before starting a pane if the project is absent +from its local registry. The router must also avoid offering such a lease. + +### 3.6 Rotation is deterministic by default + +Ordinary context-threshold rotation should not ask the model to write a +handoff. The worker derives: + +- Git anchor and branch; +- changed files and diffstat; +- last successful and failing commands; +- gate results; +- repeated tool calls and detected thrash; +- remaining acceptance criteria from `TASK.md`. + +A semantic handoff prompt is reserved for: + +- thrash where the failed approach is not mechanically clear; +- a human-facing blocker; +- unresolved design choices; +- manual rotation explicitly requesting judgment. + +Milestone rotation is disabled by default in the token-minimal policy. +Rotate for hard context pressure, thrash, quota failure, or a genuinely +independent next phase. + +--- + +## 4. Milestone 1 — Establish version and ownership truth + +### Work + +- Embed build revision, build time, and dirty status in both binaries. +- Publish coordinator build information in diagnostics. +- Include worker build information in registration and heartbeat. +- Show source revision separately for coordinator and each worker. +- Persist worker health fields: local herdr status, active task, active pane, + last error, and check time. +- Make worker registration declare supported projects as well as capacity. +- Reject leases when worker project support is absent. +- Document one deployment command and one verification command for each + binary. + +### Acceptance + +- The UI/API shows the exact coordinator and worker revisions. +- A stale worker is visible as stale without SSH. +- A worker configured only for `test-e2e` cannot receive a `correx` lease. +- Restarting the coordinator preserves worker identity and pending commands. +- `go test ./...`, `go build ./...`, and `go vet ./...` pass. + +--- + +## 5. Milestone 2 — Fix leases and session fencing + +### Work + +- Add a version-guarded worker lease-renewal endpoint. +- Renew from the worker heartbeat loop only after local pane/session + validation. +- Persist renewal and cleanup errors in the worker health model. +- Route every expiry and pane-exit release through `Router.HandleEvent`. +- Count genuine expiry/crash releases against retry policy. +- Do not count rotations or successful stage transfers as failures. +- Add a durable cleanup command for remote workers. +- Retain pane identity and cleanup state after terminal task transitions. +- Fence replacement startup when an old pane may still own the checkout. + +### Acceptance + +- A disposable task can remain leased for more than 30 minutes without being + restarted. +- Stopping the worker causes one expiry/release and one bounded retry. +- Restarting the worker recovers the existing session instead of starting a + duplicate. +- A replacement lease never overlaps an unfenced predecessor pane. +- The task record says whether cleanup is `closed`, `unreachable`, or + `unknown`. + +--- + +## 6. Milestone 3 — Transactional completion and delivery + +### Work + +- Introduce the minimal completion signal. +- Define per-project gate profiles in configuration. +- Persist the completion transaction phases. +- Run gates in the worker-owned checkout. +- Refuse completion on a dirty or failing result unless policy explicitly + permits an exception. +- Commit result work on the task branch with deterministic metadata. +- Push the result branch and verify the remote SHA. +- Generate the canonical completion report mechanically. +- Upload the report and a real native usage receipt. +- Append `TaskCompleted` only after remote verification. +- Close the exact pane, remove transient markers, and clean the worktree. +- Make every phase idempotent under worker or coordinator restart. + +### Acceptance + +A task shown as completed proves: + +- configured gates passed; +- the result commit exists; +- the configured remote contains the reported SHA; +- the completion report matches that SHA; +- quota consumption is non-zero when the harness reported usage; +- no managed agent remains attached; +- the worktree is removed or has an explicit cleanup error. + +Also prove that a failure between any two phases resumes safely without a +duplicate prompt, duplicate commit, duplicate completion event, or lost pane. + +--- + +## 7. Milestone 4 — Token-minimal launch and continuity + +### Work + +- Stop duplicating title and description in the launch prompt. +- Extend `TASK.md` with acceptance criteria, gate profile, and completion + signal. +- Replace ordinary threshold handoff prompting with a mechanically generated + checkpoint. +- Store a compact execution ledger with the handoff artifact. +- On pickup, inject or materialize the bounded continuity fields the + successor actually needs. +- Ensure `next`, `remaining`, `dead_ends`, `open_questions`, and `learned` + are either consumed or removed from the schema; never produce unused + context. +- Keep semantic handoff prompts only for thrash, blockers, open decisions, + and manual requests. +- Add rotation policy configuration, with milestone rotation off in the + token-minimal profile. + +### Acceptance + +- The initial task description enters model context once. +- Ordinary threshold rotation requires no extra model turn. +- The successor receives every retained semantic handoff field. +- Pickup verifies `TASK.md`, the pushed anchor, and the execution ledger. +- A forced rotation continues from the recorded next action without repeating + a recorded dead end. +- Usage receipts distinguish productive task tokens from rotation overhead. + +--- + +## 8. Milestone 5 — Real project-aware federation + +### Work + +- Define per-machine project checkout configuration. +- Teach workers to select repo, worktree root, remote, and gate profile by + `task.project`. +- Validate that the selected local checkout corresponds to the configured + project before creating a pane. +- Account quota by host and harness. +- Run all Git validation and cleanup on the worker that owns the checkout. +- Retire the remaining Design A bridge deployment after confirming there are + no consumers. + +### Acceptance + +- One worker can safely execute two configured projects. +- An unknown or mismatched project fails before model launch and consumes + zero model tokens. +- A real homesrv/workpc transfer validates the anchor on workpc, pushes it to + homesrv, and picks it up from a fresh checkout. +- Coordinator code never runs Git validation against a remote worker path. + +--- + +## 9. Milestone 6 — Vikunja as the automatic task source + +### Work + +- Implement Vikunja as a provider-port adapter. +- Map eligible lists/projects, labels, and statuses to Orchestra projects and + capabilities. +- Deduplicate ingest using a stable Vikunja external key. +- Poll or subscribe without creating duplicate tasks. +- Reflect completion, failure, and blockers back to Vikunja. +- Mark reflected updates so they cannot loop back into ingestion. +- Retain the Vikunja record as an external view; Orchestra remains canonical + for execution state. + +### Acceptance + +- Creating one eligible Vikunja task creates exactly one Orchestra task. +- Repeated polling creates no duplicate. +- Ineligible tasks consume zero model tokens. +- Completion updates the original Vikunja task only after the pushed result + is verified. +- Blockers and failures are reflected with actionable evidence. + +--- + +## 10. Milestone 7 — Optional writer/reviewer workflow + +This stage is optional because review consumes useful model tokens by design. +It should add no orchestration-token overhead. + +### Default policy + +- Mechanical tasks: deterministic gates only. +- Normal code tasks: writer plus gates; review is opt-in by project or task. +- High-risk/correlation tasks: writer followed by an independent reviewer. + +### Work + +- Represent implementation and review as explicit workflow stages rather + than pretending the writer's first marker means terminal completion. +- Anchor review to the pushed writer SHA. +- Route review by capability and independence policy. +- On acceptance, finalize the original task. +- On requested changes, return the task to implementation with structured + findings and the reviewed anchor. +- Bound review loops and escalate after the configured limit. +- Never ask a model to summarize a review already represented by structured + findings and Git evidence. + +### Acceptance + +- Reviewer always inspects the exact pushed writer SHA. +- Writer and reviewer cannot accidentally share the same live context when + independence is required. +- Accepted review produces one terminal completion. +- Requested changes preserve findings across the next writer lease. +- Review can be disabled without changing the base task lifecycle. + +--- + +## 11. Milestone 8 — Operator surface and morning brief + +### Work + +- Separate active work, needs-attention items, and historical E2E residue. +- Lead task details with diagnosis, last activity, pane state, and next safe + action. +- Show completion transaction phase and pushed anchor. +- Show worker-local herdr truth separately from coordinator probes. +- Expose real quota windows and uncertainty. +- Keep the morning brief a deterministic projection over events and receipts. +- Add filters for project, source, worker, state, blocker class, and age. +- Add a documented frontend build step that keeps embedded assets synchronized. + +### Acceptance + +- An operator can distinguish queued, running, approval-blocked, retrying, + delivery-in-progress, and completed work without reading logs. +- Every enabled control reaches a tested backend action. +- The brief names completed pushed SHAs, failures, blockers, pending + approvals, quota consumption, and sync state. +- No model call is used to generate the brief. + +--- + +## 12. Live proof sequence + +Use disposable tasks and avoid destructive calls against pre-existing panes. + +1. Deploy matching coordinator and worker revisions. +2. Confirm build revisions through the API. +3. Register one `test-e2e` OpenCode worker. +4. Run a task lasting longer than one lease-renewal interval. +5. Confirm no duplicate pane or prompt. +6. Complete it and verify gate, commit, push, report, event, pane closure, and + worktree cleanup. +7. Force a worker restart during the completion transaction and verify + idempotent recovery. +8. Force one context rotation and verify successor consumption. +9. Force one worker-offline expiry and bounded retry. +10. Run a task for a second configured project. +11. Ingest one disposable Vikunja task and verify reflection. +12. Only after all previous steps pass, queue a low-risk real task. + +For each live proof, record: + +- task ID and external key; +- coordinator and worker revisions; +- pane and harness session IDs; +- event sequence; +- local and remote Git SHAs; +- gate commands and results; +- usage receipt; +- cleanup result. + +--- + +## 13. Required automated coverage + +In addition to package tests: + +- completion transaction fault-injection tests at every phase boundary; +- lease-renewal, expiry, fencing, and retry tests; +- worker restart with a live session; +- coordinator restart with an active worker; +- project/repository mismatch rejection; +- real handoff consumption, not validation alone; +- non-zero federated quota receipts; +- completion cleanup and remote-SHA verification; +- Vikunja deduplication and reflection-loop prevention; +- an end-to-end worker test that exercises launch through pushed completion; +- `go test ./...`, `go build ./...`, and `go vet ./...`; +- frontend build, typecheck, lint, and browser smoke once UI work resumes. + +Tests that stop at an isolated package seam do not prove completion. At least +one test must traverse the same coordinator → router → worker → herdr adapter +→ Git → coordinator path used in deployment. + +--- + +## 14. Deferred work + +Do not prioritize these before the live proof sequence succeeds: + +- UI cosmetics beyond diagnosis and safe controls; +- additional notification channels; +- model-generated standups or summaries; +- automatic task decomposition; +- cheap-model subdelegation; +- speculative capability vocabularies; +- schema/backend optimization that is not required by a real run; +- more harnesses before one harness completes the lifecycle reliably. + +--- + +## 15. Decisions to ratify + +Defaults used by this plan: + +1. **Token goal:** zero model-token orchestration overhead; implementation and + explicitly requested review remain legitimate token spend. +2. **Review:** opt-in for ordinary code, required only for configured + high-risk work. +3. **Completion signal:** a minimal marker; the worker authors the report. +4. **Rotation:** no model-authored handoff for ordinary context thresholds. +5. **Milestone rotation:** disabled in the token-minimal profile. +6. **Affinity:** hard; a task waits rather than running on an unconfigured + machine or checkout. +7. **Delivery:** a verified remote SHA is mandatory before `TaskCompleted`. + +Changing any of these defaults should update this file and the binding spec +before implementation. + diff --git a/cmd/orchestra-password/main.go b/cmd/orchestra-password/main.go new file mode 100644 index 0000000..2fc4287 --- /dev/null +++ b/cmd/orchestra-password/main.go @@ -0,0 +1,27 @@ +// orchestra-password prints a bcrypt hash suitable for ORCHESTRA_WEB_PASSWORD_HASH. +package main + +import ( + "fmt" + "os" + "syscall" + + "golang.org/x/crypto/bcrypt" + "golang.org/x/term" +) + +func main() { + fmt.Fprint(os.Stderr, "Password: ") + password, err := term.ReadPassword(int(syscall.Stdin)) + fmt.Fprintln(os.Stderr) + if err != nil || len(password) == 0 { + fmt.Fprintln(os.Stderr, "password is required") + os.Exit(1) + } + hash, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost) + if err != nil { + fmt.Fprintln(os.Stderr, "hash password:", err) + os.Exit(1) + } + fmt.Println(string(hash)) +} diff --git a/cmd/orchestra-worker/main.go b/cmd/orchestra-worker/main.go index 1964a88..ff78b74 100644 --- a/cmd/orchestra-worker/main.go +++ b/cmd/orchestra-worker/main.go @@ -36,6 +36,7 @@ type worker struct { sessions map[string]herdr.Session leases map[string]lease releases map[string]releaseTransaction + quarantined map[string]bool statePath string hard float64 registration federation.Worker @@ -79,6 +80,7 @@ func (w *worker) health(ctx context.Context) federation.WorkerHealth { } type lease struct { + Epoch string `json:"epoch"` HandoffRef string `json:"handoff_ref,omitempty"` TransactionID string `json:"transaction_id,omitempty"` AnchorSHA string `json:"anchor_sha,omitempty"` @@ -117,24 +119,29 @@ type completionEvidence struct { CompletedAt time.Time `json:"completed_at"` } type workerState struct { - Cursor uint64 `json:"cursor"` - Sessions map[string]herdr.Session `json:"sessions"` - Tasks map[string]domain.Task `json:"tasks"` - Leases map[string]lease `json:"leases"` - Releases map[string]releaseTransaction `json:"releases"` + Cursor uint64 `json:"cursor"` + Sessions map[string]herdr.Session `json:"sessions"` + Tasks map[string]domain.Task `json:"tasks"` + Leases map[string]lease `json:"leases"` + Releases map[string]releaseTransaction `json:"releases"` + Quarantined map[string]bool `json:"quarantined,omitempty"` } -func (w *worker) load() { +func (w *worker) load() error { b, e := os.ReadFile(w.statePath) if e == nil { var s workerState - if json.Unmarshal(b, &s) == nil { - w.cursor = s.Cursor - w.sessions = s.Sessions - w.tasks = s.Tasks - w.leases = s.Leases - w.releases = s.Releases + if err := json.Unmarshal(b, &s); err != nil { + return fmt.Errorf("corrupt worker state %s: %w", w.statePath, err) } + w.cursor = s.Cursor + w.sessions = s.Sessions + w.tasks = s.Tasks + w.leases = s.Leases + w.releases = s.Releases + w.quarantined = s.Quarantined + } else if !errors.Is(e, os.ErrNotExist) { + return fmt.Errorf("read worker state %s: %w", w.statePath, e) } if w.sessions == nil { w.sessions = map[string]herdr.Session{} @@ -148,16 +155,68 @@ func (w *worker) load() { if w.releases == nil { w.releases = map[string]releaseTransaction{} } + if w.quarantined == nil { + w.quarantined = map[string]bool{} + } + return nil } func (w *worker) save() error { - b, e := json.Marshal(workerState{Cursor: w.cursor, Sessions: w.sessions, Tasks: w.tasks, Leases: w.leases, Releases: w.releases}) + b, e := json.Marshal(workerState{Cursor: w.cursor, Sessions: w.sessions, Tasks: w.tasks, Leases: w.leases, Releases: w.releases, Quarantined: w.quarantined}) if e != nil { return e } if e := os.MkdirAll(filepath.Dir(w.statePath), 0700); e != nil { return e } - return os.WriteFile(w.statePath, b, 0600) + tmp := w.statePath + ".tmp" + f, e := os.OpenFile(tmp, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + if e != nil { + return e + } + if _, e = f.Write(b); e == nil { + e = f.Sync() + } + if closeErr := f.Close(); e == nil { + e = closeErr + } + if e != nil { + _ = os.Remove(tmp) + return e + } + if e = os.Rename(tmp, w.statePath); e != nil { + return e + } + dir, e := os.Open(filepath.Dir(w.statePath)) + if e != nil { + return e + } + defer dir.Close() + return dir.Sync() +} + +// quarantine stops a pane before its lost lease mapping can be forgotten. +// A failed close remains durable and is retried; it is never treated as a +// harmless cleanup error while the old harness could still be working. +func (w *worker) quarantine(ctx context.Context, taskID string, s herdr.Session) { + if w.herdr == nil { + w.quarantined[taskID] = true + return + } + if err := (herdr.CLIAdapter{Client: w.herdr, Harness: w.harness}).Kill(ctx, s); err != nil { + w.quarantined[taskID] = true + w.recordError(fmt.Errorf("quarantine %s: %w", taskID, err)) + return + } + delete(w.sessions, taskID) + delete(w.quarantined, taskID) +} + +func (w *worker) retryQuarantines(ctx context.Context) { + for taskID := range w.quarantined { + if s, ok := w.sessions[taskID]; ok { + w.quarantine(ctx, taskID, s) + } + } } type artifactCAS struct{ api federation.Client } @@ -286,6 +345,9 @@ func taskHash(t domain.Task) string { b := continuity.RenderTaskFile(t); return func (w *worker) releaseReady(ctx context.Context) { for id, s := range w.sessions { + if w.quarantined[id] { + continue + } if l := w.leases[id]; l.HandoffRef != "" && !l.PickupAcknowledged { if err := w.ackPickup(ctx, id, s); err != nil { w.recordError(err) @@ -306,7 +368,7 @@ func (w *worker) releaseReady(ctx context.Context) { log.Printf("upload completion %s: %v", id, err) continue } - if err = w.api.Complete(ctx, id, ref, evidence.ResultSHA, evidence.Branch, evidence.Remote, w.leases[id].Version, w.usageReceipt(s), w.sessionEvidence(ctx, id, s)); err != nil { + if err = w.api.Complete(ctx, id, ref, evidence.ResultSHA, evidence.Branch, evidence.Remote, w.leases[id].Epoch, w.leases[id].Version, w.usageReceipt(s), w.sessionEvidence(ctx, id, s)); err != nil { w.recordError(fmt.Errorf("complete %s: %w", id, err)) log.Printf("complete %s: %v", id, err) continue @@ -439,7 +501,8 @@ func (w *worker) advanceRelease(ctx context.Context, id string, s herdr.Session) _ = w.save() } if tx.Phase == "anchor_pushed" { - if err := w.api.Release(ctx, id, tx.Ref, tx.AnchorSHA, tx.ID, tx.LeaseVersion, w.sessionEvidence(ctx, id, s)); err != nil { + l := w.leases[id] + if err := w.api.Release(ctx, id, tx.Ref, tx.AnchorSHA, tx.ID, l.Epoch, tx.LeaseVersion, w.sessionEvidence(ctx, id, s)); err != nil { tx.LastError, tx.UpdatedAt = err.Error(), time.Now().UTC() w.releases[id] = tx _ = w.save() @@ -491,7 +554,7 @@ func (w *worker) ackPickup(ctx context.Context, id string, s herdr.Session) erro if l.PickupAcknowledged { return nil } - if err := w.api.Pickup(ctx, id, l.HandoffRef, l.AnchorSHA, l.TransactionID, l.Version, w.sessionEvidence(ctx, id, s)); err != nil { + if err := w.api.Pickup(ctx, id, l.HandoffRef, l.AnchorSHA, l.TransactionID, l.Epoch, l.Version, w.sessionEvidence(ctx, id, s)); err != nil { return fmt.Errorf("pickup %s acknowledgement: %w", id, err) } l.PickupAcknowledged = true @@ -617,7 +680,7 @@ func (w *worker) renewLeases(ctx context.Context) { w.recordError(fmt.Errorf("validate lease %s: %w", taskID, err)) continue } - if err := w.api.Renew(ctx, taskID, l.Version, int((30 * time.Minute).Seconds())); err != nil { + if err := w.api.Renew(ctx, taskID, l.Epoch, l.Version, int((30 * time.Minute).Seconds())); err != nil { w.recordError(fmt.Errorf("renew lease %s: %w", taskID, err)) log.Printf("renew lease %s: %v", taskID, err) } else { @@ -726,6 +789,7 @@ func (w *worker) once(ctx context.Context) error { if e.Type == "TaskLeased" { var p struct { HarnessID string `json:"harness_id"` + Epoch string `json:"lease_epoch"` HandoffRef string `json:"handoff_ref"` TransactionID string `json:"transaction_id"` AnchorSHA string `json:"anchor_sha"` @@ -735,17 +799,18 @@ func (w *worker) once(ctx context.Context) error { UntilNS int64 `json:"until_ns"` } _ = json.Unmarshal(e.Payload, &until) - w.leases[e.TaskID] = lease{HandoffRef: p.HandoffRef, TransactionID: p.TransactionID, AnchorSHA: p.AnchorSHA, Version: e.Version, Until: time.Unix(0, until.UntilNS)} + w.leases[e.TaskID] = lease{Epoch: p.Epoch, HandoffRef: p.HandoffRef, TransactionID: p.TransactionID, AnchorSHA: p.AnchorSHA, Version: e.Version, Until: time.Unix(0, until.UntilNS)} } } if e.Type == "TaskLeaseRenewed" { var p struct { HarnessID string `json:"harness_id"` + Epoch string `json:"lease_epoch"` UntilNS int64 `json:"until_ns"` } if json.Unmarshal(e.Payload, &p) == nil && p.HarnessID == w.harnessID { l := w.leases[e.TaskID] - l.Version, l.Until = e.Version, time.Unix(0, p.UntilNS) + l.Version, l.Epoch, l.Until = e.Version, p.Epoch, time.Unix(0, p.UntilNS) w.leases[e.TaskID] = l } } @@ -755,6 +820,7 @@ func (w *worker) once(ctx context.Context) error { if w.herdr == nil { delete(w.sessions, e.TaskID) } else if err := (herdr.CLIAdapter{Client: w.herdr, Harness: w.harness}).Kill(ctx, session); err != nil { + w.quarantined[e.TaskID] = true w.recordError(fmt.Errorf("close completed pane %s: %w", e.TaskID, err)) continue } else { @@ -796,7 +862,9 @@ func (w *worker) once(ctx context.Context) error { // TaskPickupValidated for its transaction. Do not erase its pane // mapping merely because our own release event was replayed. if _, releasing := w.releases[e.TaskID]; !releasing { - delete(w.sessions, e.TaskID) + if session, active := w.sessions[e.TaskID]; active { + w.quarantine(ctx, e.TaskID, session) + } } } if e.Seq > w.cursor { @@ -848,6 +916,7 @@ func (w *worker) once(ctx context.Context) error { w.runCommands(ctx) w.renewLeases(ctx) } + w.retryQuarantines(ctx) w.releaseReady(ctx) if err := w.save(); err != nil { return err @@ -864,7 +933,7 @@ func (w *worker) reconcileLeases(ctx context.Context) error { for _, task := range tasks { w.tasks[task.ID] = task if task.State == domain.StateLeased && task.Lease != nil && task.Lease.HarnessID == w.harnessID { - active[task.ID] = lease{HandoffRef: task.HandoffRef, TransactionID: task.ReleaseTransaction, AnchorSHA: task.ReleaseAnchor, Version: task.Version, Until: task.Lease.Until} + active[task.ID] = lease{Epoch: task.Lease.Epoch, HandoffRef: task.HandoffRef, TransactionID: task.ReleaseTransaction, AnchorSHA: task.ReleaseAnchor, Version: task.Version, Until: task.Lease.Until} } } for taskID := range w.leases { @@ -951,11 +1020,13 @@ func main() { if v, err := strconv.ParseInt(os.Getenv("ORCHESTRA_CONTEXT_WINDOW"), 10, 64); err == nil && v > 0 { window = v } - w := &worker{api: federation.Client{BaseURL: required("ORCHESTRA_URL"), WorkerID: id, Token: token, AdmitToken: os.Getenv("ORCHESTRA_FEDERATION_ADMIT_TOKEN")}, harnessID: required("ORCHESTRA_WORKER_HERDR_ID"), harness: required("ORCHESTRA_WORKER_HARNESS"), repo: repo, root: root, remote: remote, projects: projects, tasks: map[string]domain.Task{}, sessions: map[string]herdr.Session{}, leases: map[string]lease{}, releases: map[string]releaseTransaction{}, statePath: os.Getenv("ORCHESTRA_WORKER_STATE"), hard: hard, soft: soft, window: window, registration: federation.Worker{ID: id, Address: os.Getenv("ORCHESTRA_WORKER_ADDRESS"), Capacity: 1, SupportedProjects: supported, Build: buildinfo.Current()}} + w := &worker{api: federation.Client{BaseURL: required("ORCHESTRA_URL"), WorkerID: id, Token: token, AdmitToken: os.Getenv("ORCHESTRA_FEDERATION_ADMIT_TOKEN")}, harnessID: required("ORCHESTRA_WORKER_HERDR_ID"), harness: required("ORCHESTRA_WORKER_HARNESS"), repo: repo, root: root, remote: remote, projects: projects, tasks: map[string]domain.Task{}, sessions: map[string]herdr.Session{}, leases: map[string]lease{}, releases: map[string]releaseTransaction{}, quarantined: map[string]bool{}, statePath: os.Getenv("ORCHESTRA_WORKER_STATE"), hard: hard, soft: soft, window: window, registration: federation.Worker{ID: id, Address: os.Getenv("ORCHESTRA_WORKER_ADDRESS"), Capacity: 1, SupportedProjects: supported, Build: buildinfo.Current()}} if w.statePath == "" { w.statePath = filepath.Join(w.root, ".orchestra-worker-state.json") } - w.load() + if err := w.load(); err != nil { + log.Fatal(err) + } w.herdr = herdr.New(required("ORCHESTRA_WORKER_HERDR")) if id != w.harnessID { log.Fatal("ORCHESTRA_WORKER_ID must equal ORCHESTRA_WORKER_HERDR_ID so leases and offline recovery have one owner") diff --git a/cmd/orchestra-worker/main_test.go b/cmd/orchestra-worker/main_test.go index 60fa82f..144b20a 100644 --- a/cmd/orchestra-worker/main_test.go +++ b/cmd/orchestra-worker/main_test.go @@ -11,10 +11,12 @@ import ( "orchestra/internal/domain" "orchestra/internal/federation" "orchestra/internal/herdr" + "orchestra/internal/orchestrator" "os" "os/exec" "path/filepath" "reflect" + "strings" "testing" "time" ) @@ -38,6 +40,17 @@ func TestWorkerReregistersAfterCoordinatorForgetsIt(t *testing.T) { } } +func TestWorkerRefusesCorruptDurableState(t *testing.T) { + path := filepath.Join(t.TempDir(), "state.json") + if err := os.WriteFile(path, []byte(`{"cursor":`), 0600); err != nil { + t.Fatal(err) + } + w := &worker{statePath: path} + if err := w.load(); err == nil { + t.Fatal("corrupt worker state was accepted") + } +} + func TestInitialReplayDoesNotResurrectReleasedLease(t *testing.T) { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { @@ -175,6 +188,55 @@ func TestWorkerStartsRouterIssuedLeaseInLocalGitWorktree(t *testing.T) { } } +func TestFinalizeRunsGateCommitsPushesAndVerifiesRemote(t *testing.T) { + remote := filepath.Join(t.TempDir(), "remote.git") + if out, err := exec.Command("git", "init", "--bare", remote).CombinedOutput(); err != nil { + t.Fatalf("remote: %v %s", err, out) + } + seed := t.TempDir() + for _, a := range [][]string{{"init", seed}, {"-C", seed, "config", "user.email", "t@t"}, {"-C", seed, "config", "user.name", "t"}, {"-C", seed, "commit", "--allow-empty", "-m", "init"}, {"-C", seed, "remote", "add", "origin", remote}, {"-C", seed, "push", "-u", "origin", "HEAD:master"}} { + if out, err := exec.Command("git", a...).CombinedOutput(); err != nil { + t.Fatalf("git %v: %v %s", a, err, out) + } + } + repo := filepath.Join(t.TempDir(), "repo") + if out, err := exec.Command("git", "clone", remote, repo).CombinedOutput(); err != nil { + t.Fatalf("clone: %v %s", err, out) + } + for _, a := range [][]string{{"-C", repo, "config", "user.email", "t@t"}, {"-C", repo, "config", "user.name", "t"}} { + if out, err := exec.Command("git", a...).CombinedOutput(); err != nil { + t.Fatalf("git %v: %v %s", a, err, out) + } + } + root := filepath.Join(t.TempDir(), "worktrees") + task := domain.Task{ID: "task", Source: "s", ExternalID: "delivery", Project: "p", QualityGate: "test -f result.txt"} + wt, err := (orchestrator.GitWorktrees{Repo: repo, Root: root}).Create(context.Background(), task) + if err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(wt, "result.txt"), []byte("delivered"), 0644); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(filepath.Join(wt, ".orchestra"), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(wt, ".orchestra", "done"), nil, 0644); err != nil { + t.Fatal(err) + } + w := &worker{harnessID: "worker", harness: "opencode", repo: repo, root: root, remote: "origin", tasks: map[string]domain.Task{task.ID: task}} + evidence, err := w.finalize(context.Background(), task.ID, herdr.Session{PaneID: "pane", Worktree: wt, TaskFileSHA: taskHash(task)}) + if err != nil { + t.Fatal(err) + } + if evidence.ResultSHA == evidence.BaseSHA || evidence.Branch != "orchestra/task" || evidence.QualityGate != task.QualityGate { + t.Fatalf("unexpected evidence: %+v", evidence) + } + out, err := exec.Command("git", "-C", repo, "ls-remote", "origin", "refs/heads/orchestra/task").CombinedOutput() + if err != nil || !strings.HasPrefix(string(out), evidence.ResultSHA+"\t") { + t.Fatalf("remote result=%q err=%v want %s", out, err, evidence.ResultSHA) + } +} + func TestWorkerApprovalCommandIsRevisionBoundAndAcknowledged(t *testing.T) { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { diff --git a/cmd/orchestra/main.go b/cmd/orchestra/main.go index 73c4210..04fb534 100644 --- a/cmd/orchestra/main.go +++ b/cmd/orchestra/main.go @@ -142,12 +142,14 @@ func (h harnessCompletion) ServeHTTP(w http.ResponseWriter, r *http.Request) { } var p struct { TaskID string `json:"task_id"` + WorkerID string `json:"worker_id"` + LeaseEpoch string `json:"lease_epoch"` Harness string `json:"harness"` TranscriptPath string `json:"transcript_path"` Report string `json:"report"` } - if json.NewDecoder(r.Body).Decode(&p) != nil || p.TaskID == "" || p.Report == "" || p.TranscriptPath == "" { - http.Error(w, "task_id, transcript_path, and report are required", http.StatusBadRequest) + if json.NewDecoder(r.Body).Decode(&p) != nil || p.TaskID == "" || p.WorkerID == "" || p.LeaseEpoch == "" || p.Report == "" || p.TranscriptPath == "" { + http.Error(w, "task_id, worker_id, lease_epoch, transcript_path, and report are required", http.StatusBadRequest) return } t, ok := h.store.Task(p.TaskID) @@ -155,6 +157,10 @@ func (h harnessCompletion) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, "task not found", http.StatusNotFound) return } + if t.State != domain.StateLeased || t.Lease == nil || t.Lease.HarnessID != p.WorkerID || t.Lease.Epoch != p.LeaseEpoch { + http.Error(w, "lease not owned", http.StatusConflict) + return + } var usage herdr.Usage var err error switch p.Harness { @@ -177,7 +183,7 @@ func (h harnessCompletion) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - payload, _ := json.Marshal(map[string]any{"report_ref": ref, "receipt": map[string]any{ + payload, _ := json.Marshal(map[string]any{"report_ref": ref, "harness_id": p.WorkerID, "lease_epoch": p.LeaseEpoch, "expected_version": t.Version, "receipt": map[string]any{ "input_tokens": usage.Input, "cache_read_tokens": usage.CacheRead, "cache_write_tokens": usage.CacheWrite, "output_tokens": usage.Output, "numerator": usage.Numerator(), }}) @@ -381,17 +387,10 @@ func main() { return err }}.Handler()) mux.Handle("/", webui.Handler()) - workers.OnOffline = func(w federation.Worker) { - for _, t := range s.Tasks() { - if t.State == domain.StateLeased && t.Lease != nil && t.Lease.HarnessID == w.ID { - p, _ := json.Marshal(map[string]any{"reason": "worker_offline", "harness_id": w.ID}) - e := domain.Event{ID: id(), Type: "TaskReleased", TaskID: t.ID, Version: t.Version + 1, Payload: p, Surface: string(authz.System)} - if err := s.Append(e); err == nil && rt != nil { - _, _ = rt.HandleEvent(e) - } - } - } - } + // A missed heartbeat is not relinquishment. Releasing here used to lease + // the same task to a successor while the old pane was still running. The + // authoritative lease timer performs the only automatic reassignment. + workers.OnOffline = func(w federation.Worker) { log.Printf("worker %s offline; retaining leases until expiry", w.ID) } go func() { ticker := time.NewTicker(15 * time.Second) defer ticker.Stop() @@ -560,13 +559,12 @@ func main() { // same session-file assumption as CLIAdapter.Occupancy — rather than // trusting a self-reported number. harnessToken := os.Getenv("ORCHESTRA_HARNESS_TOKEN") - mux.Handle("/v1/harness/complete", harnessCompletion{store: s, token: harnessToken, route: func(e domain.Event) error { - if rt == nil { - return nil - } - _, err := rt.HandleEvent(e) - return err - }}) + // The unaffiliated harness hook has no durable worker identity or fencing + // epoch, so it cannot safely mutate a leased task. Completion is accepted + // only through the authenticated federation worker endpoint below. + mux.HandleFunc("/v1/harness/complete", func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "legacy harness completion endpoint retired; use worker completion", http.StatusGone) + }) // /v1/harness/turn is the unified turn-decision endpoint (AUDIT.md Phase // 2 items 1-2): the Face-B stop hook posts here on every ordinary turn // boundary (report marker absent — /v1/harness/complete covers task @@ -1062,7 +1060,7 @@ func main() { w.WriteHeader(http.StatusNoContent) }) mux.HandleFunc("/v1/federation/workers/", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost || (!strings.HasSuffix(r.URL.Path, "/heartbeat") && !strings.HasSuffix(r.URL.Path, "/renew") && !strings.HasSuffix(r.URL.Path, "/handoff") && !strings.HasSuffix(r.URL.Path, "/complete") && !strings.HasSuffix(r.URL.Path, "/captures")) { + if r.Method != http.MethodPost || (!strings.HasSuffix(r.URL.Path, "/heartbeat") && !strings.HasSuffix(r.URL.Path, "/renew") && !strings.HasSuffix(r.URL.Path, "/handoff") && !strings.HasSuffix(r.URL.Path, "/pickup") && !strings.HasSuffix(r.URL.Path, "/complete") && !strings.HasSuffix(r.URL.Path, "/captures")) { http.Error(w, "not found", 404) return } @@ -1119,6 +1117,7 @@ func main() { AnchorSHA string `json:"anchor_sha"` TransactionID string `json:"transaction_id"` LeaseVersion int `json:"lease_version"` + LeaseEpoch string `json:"lease_epoch"` ResultSHA string `json:"result_sha"` Branch string `json:"branch"` Remote string `json:"remote"` @@ -1134,7 +1133,7 @@ func main() { http.Error(w, "task not found", 404) return } - ownedLease := t.State == domain.StateLeased && t.Lease != nil && t.Lease.HarnessID == parts[3] + ownedLease := t.State == domain.StateLeased && t.Lease != nil && t.Lease.HarnessID == parts[3] && t.Lease.Epoch == b.LeaseEpoch // A response can be lost after the append/fsync. Retrying the exact // release transaction is therefore a successful no-op, never a second // TaskReleased event and never a reason to discard the predecessor. @@ -1142,12 +1141,7 @@ func main() { w.WriteHeader(http.StatusNoContent) return } - // A prompt timeout can block the coordinator after herdr already - // accepted the request. If that same authenticated worker later reports - // a durable completion, reconcile it rather than preserving a known - // false blocked state. No other blocked task is admitted here. - recoverableBlocked := strings.HasSuffix(r.URL.Path, "/complete") && t.State == domain.StateBlocked && t.LastHarness == parts[3] - if !ownedLease && !recoverableBlocked { + if !ownedLease { http.Error(w, "lease not owned", 409) return } @@ -1160,7 +1154,7 @@ func main() { if ttl == 0 { ttl = int((30 * time.Minute).Seconds()) } - e, err := s.RenewLease(b.TaskID, parts[3], b.ExpectedVersion, time.Duration(ttl)*time.Second) + e, err := s.RenewLease(b.TaskID, parts[3], b.LeaseEpoch, b.ExpectedVersion, time.Duration(ttl)*time.Second) if err != nil { http.Error(w, err.Error(), http.StatusConflict) return @@ -1181,7 +1175,7 @@ func main() { http.Error(w, "lease version conflict", http.StatusConflict) return } - p, _ := json.Marshal(map[string]any{"transaction_id": b.TransactionID, "handoff_ref": b.HandoffRef, "anchor_sha": b.AnchorSHA, "harness_id": parts[3], "lease_version": b.LeaseVersion, "expected_version": t.Version, "session_evidence": b.SessionEvidence}) + p, _ := json.Marshal(map[string]any{"transaction_id": b.TransactionID, "handoff_ref": b.HandoffRef, "anchor_sha": b.AnchorSHA, "harness_id": parts[3], "lease_epoch": b.LeaseEpoch, "lease_version": b.LeaseVersion, "expected_version": t.Version, "session_evidence": b.SessionEvidence}) e := domain.Event{ID: id(), Type: "TaskPickupValidated", TaskID: b.TaskID, Version: t.Version + 1, Payload: p, Surface: string(authz.System)} if err := s.Append(e); err != nil { http.Error(w, err.Error(), http.StatusConflict) @@ -1206,7 +1200,7 @@ func main() { if _, ok := b.Receipt["consumed"]; !ok { b.Receipt["consumed"] = 0 } - p, _ := json.Marshal(map[string]any{"report_ref": b.HandoffRef, "receipt": b.Receipt, "result_sha": b.ResultSHA, "branch": b.Branch, "remote": b.Remote, "session_evidence": b.SessionEvidence}) + p, _ := json.Marshal(map[string]any{"report_ref": b.HandoffRef, "receipt": b.Receipt, "result_sha": b.ResultSHA, "branch": b.Branch, "remote": b.Remote, "harness_id": parts[3], "lease_epoch": b.LeaseEpoch, "expected_version": t.Version, "session_evidence": b.SessionEvidence}) e := domain.Event{ID: id(), Type: "TaskCompleted", TaskID: b.TaskID, Version: t.Version + 1, Payload: p, Surface: string(authz.System)} if err := s.Append(e); err != nil { http.Error(w, err.Error(), 409) @@ -1233,7 +1227,7 @@ func main() { http.Error(w, "release transaction and current lease version required", http.StatusConflict) return } - p, _ := json.Marshal(map[string]any{"handoff_ref": b.HandoffRef, "harness_id": parts[3], "anchor_sha": b.AnchorSHA, "transaction_id": b.TransactionID, "expected_version": t.Version, "session_evidence": b.SessionEvidence}) + p, _ := json.Marshal(map[string]any{"handoff_ref": b.HandoffRef, "harness_id": parts[3], "lease_epoch": b.LeaseEpoch, "anchor_sha": b.AnchorSHA, "transaction_id": b.TransactionID, "expected_version": t.Version, "session_evidence": b.SessionEvidence}) e := domain.Event{ID: id(), Type: "TaskReleased", TaskID: b.TaskID, Version: t.Version + 1, Payload: p, Surface: string(authz.System)} if err := s.Append(e); err != nil { http.Error(w, err.Error(), 409) diff --git a/cmd/orchestra/main_test.go b/cmd/orchestra/main_test.go index 2e06169..da3b8ae 100644 --- a/cmd/orchestra/main_test.go +++ b/cmd/orchestra/main_test.go @@ -41,6 +41,20 @@ func TestFederatedReachabilityDefersRemoteHerdrToWorkerHeartbeat(t *testing.T) { } } +func TestCoordinatorOwnsOnlyLocalHerdrInFederationMode(t *testing.T) { + local := registry.Herdr{ID: "homesrv-opencode", MachineID: "homesrv"} + remote := registry.Herdr{ID: "workpc-opencode", MachineID: "workpc"} + if !coordinatorOwnsHerdr(local, "homesrv") { + t.Fatal("coordinator does not own its local herdr") + } + if coordinatorOwnsHerdr(remote, "homesrv") { + t.Fatal("coordinator claimed a worker-owned remote herdr") + } + if !coordinatorOwnsHerdr(remote, "") { + t.Fatal("single-machine mode should retain legacy local ownership") + } +} + func TestHarnessCompletionBuildsReceiptAndQuotaFromTranscript(t *testing.T) { s, err := store.Open(t.TempDir()) if err != nil { @@ -56,7 +70,8 @@ func TestHarnessCompletionBuildsReceiptAndQuotaFromTranscript(t *testing.T) { if err := os.WriteFile(transcript, []byte(`{"message":{"usage":{"input_tokens":100,"cache_read_input_tokens":20,"cache_creation_input_tokens":5,"output_tokens":7}}}`+"\n"), 0600); err != nil { t.Fatal(err) } - body, _ := json.Marshal(map[string]string{"task_id": "done", "transcript_path": transcript, "report": "# done"}) + task, _ := s.Task("done") + body, _ := json.Marshal(map[string]string{"task_id": "done", "worker_id": "local-claude", "lease_epoch": task.Lease.Epoch, "transcript_path": transcript, "report": "# done"}) req := httptest.NewRequest(http.MethodPost, "/v1/harness/complete", bytes.NewReader(body)) res := httptest.NewRecorder() harnessCompletion{store: s, token: "secret"}.ServeHTTP(res, req) diff --git a/deploy/DEPLOYMENT.md b/deploy/DEPLOYMENT.md new file mode 100644 index 0000000..d3a4d2d --- /dev/null +++ b/deploy/DEPLOYMENT.md @@ -0,0 +1,43 @@ +# Deployment verification + +Both binaries embed their Git revision, UTC build time, and dirty flag. Build +the coordinator with `deploy/redeploy.sh`; it installs and restarts the local +`orchestra.service`. + +## Browser operator login + +The browser UI requires `ORCHESTRA_WEB_USERNAME` and +`ORCHESTRA_WEB_PASSWORD_HASH`. Generate a bcrypt hash without putting the +password in shell history: + +```sh +go run ./cmd/orchestra-password +``` + +Set the emitted hash in the service environment along with the chosen +username, then restart the coordinator. `ORCHESTRA_WEB_TOKEN` is not used by +the browser UI anymore. + +Build a worker for staging on workpc with: + +```sh +revision=$(git rev-parse HEAD) +build_time=$(date -u +%Y-%m-%dT%H:%M:%SZ) +dirty=false; test -z "$(git status --porcelain)" || dirty=true +go build -ldflags "-X orchestra/internal/buildinfo.Revision=$revision -X orchestra/internal/buildinfo.Time=$build_time -X orchestra/internal/buildinfo.Dirty=$dirty" -o orchestra-worker ./cmd/orchestra-worker +scp orchestra-worker workpc:~/orchestra-deploy/orchestra-worker +ssh workpc 'sha256sum ~/orchestra-deploy/orchestra-worker' +``` + +The worker receives only the path to a normal project configuration file: +`ORCHESTRA_WORKER_PROJECT_CONFIG_FILE=/etc/orchestra/worker-projects.json`. +That file contains a JSON object whose project entries contain `repo`, +`worktree_root`, and `remote`; mount or provision it like any other worker +configuration. The legacy single-checkout `ORCHESTRA_WORKER_PROJECTS` comma +list remains supported for one existing checkout. An absent project is +ineligible for routing. + +Verify the coordinator at `GET /v1/admin/diagnostics` with the normal admin +credential: its `build` object is the coordinator provenance. `GET +/v1/federation/workers` shows every worker's `build`, supported projects, and +worker-local health without SSH. diff --git a/deploy/config.example.jsonc b/deploy/config.example.jsonc index f6eca44..327c679 100644 --- a/deploy/config.example.jsonc +++ b/deploy/config.example.jsonc @@ -10,8 +10,9 @@ // Required — a project with no affinity can't be routed. "repo": "/var/lib/orchestra/repos/correx.git", // Optional per-project git repo path. // Overrides the global ORCHESTRA_REPO default. - "worktree_root": "/var/lib/orchestra/worktrees/correx" // Optional per-project worktree dir. + "worktree_root": "/var/lib/orchestra/worktrees/correx", // Optional per-project worktree dir. // Overrides global ORCHESTRA_WORKTREE_ROOT. + "quality_gate": "go test ./... && go vet ./..." // Worker runs this before deterministic delivery. }, { "id": "maven", diff --git a/deploy/redeploy.sh b/deploy/redeploy.sh index c4eb98f..eb835e8 100755 --- a/deploy/redeploy.sh +++ b/deploy/redeploy.sh @@ -7,7 +7,11 @@ trap 'rm -f -- "$tmp_bin"' EXIT cd "$repo_dir" echo "Building Orchestra..." -go build -o "$tmp_bin" ./cmd/orchestra +revision="$(git rev-parse HEAD)" +build_time="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +dirty=false +if [[ -n "$(git status --porcelain)" ]]; then dirty=true; fi +go build -ldflags "-X orchestra/internal/buildinfo.Revision=$revision -X orchestra/internal/buildinfo.Time=$build_time -X orchestra/internal/buildinfo.Dirty=$dirty" -o "$tmp_bin" ./cmd/orchestra echo "Installing /usr/local/bin/orchestra..." sudo install -o root -g root -m 0755 "$tmp_bin" /usr/local/bin/orchestra diff --git a/deploy/worker-projects.example.json b/deploy/worker-projects.example.json new file mode 100644 index 0000000..41094ab --- /dev/null +++ b/deploy/worker-projects.example.json @@ -0,0 +1,14 @@ +{ + "test-e2e": { + "repo": "/srv/orchestra/repos/test-e2e", + "worktree_root": "/srv/orchestra/worktrees/test-e2e", + "remote": "origin", + "quality_gate": "go test ./..." + }, + "correx": { + "repo": "/srv/orchestra/repos/correx", + "worktree_root": "/srv/orchestra/worktrees/correx", + "remote": "origin", + "quality_gate": "go test ./... && go vet ./..." + } +} diff --git a/go.mod b/go.mod index f618340..af0256c 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,10 @@ module orchestra go 1.22 + +require ( + golang.org/x/crypto v0.29.0 + golang.org/x/term v0.26.0 +) + +require golang.org/x/sys v0.27.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7a5ab42 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= diff --git a/internal/admin/admin.go b/internal/admin/admin.go index d4e99d1..3ee7e40 100644 --- a/internal/admin/admin.go +++ b/internal/admin/admin.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" "orchestra/internal/authz" + "orchestra/internal/buildinfo" "orchestra/internal/domain" "orchestra/internal/provider" "orchestra/internal/store" @@ -51,6 +52,7 @@ type ProbeFunc func() (bool, string) type Server struct { Store *store.Store RouterReady bool + Build buildinfo.Info Probes map[string]ProbeFunc Providers map[string]*provider.Supervisor } @@ -95,7 +97,7 @@ func (s *Server) Diagnostics(w http.ResponseWriter, r *http.Request) { } tasks := s.Store.Tasks() events := s.Store.Events(0) - _ = json.NewEncoder(w).Encode(map[string]any{"tasks": len(tasks), "events": len(events), "last_seq": func() uint64 { + _ = json.NewEncoder(w).Encode(map[string]any{"build": s.Build, "tasks": len(tasks), "events": len(events), "last_seq": func() uint64 { if len(events) == 0 { return 0 } diff --git a/internal/authz/authz.go b/internal/authz/authz.go index bf65898..20ac9ca 100644 --- a/internal/authz/authz.go +++ b/internal/authz/authz.go @@ -12,6 +12,8 @@ import ( "strings" "sync" "time" + + "golang.org/x/crypto/bcrypt" ) type Surface string @@ -72,18 +74,44 @@ func AuthorizeEvent(s Surface, typ string) error { return nil } -// SessionCookie carries a browser's proof of the Web-surface token. A -// top-level document load cannot set an Authorization header, so a -// bearer-only gate forces operators to run the UI with no token at all -// (AUDIT.md B18). The cookie is the browser-presentable equivalent; it is -// never a second credential, only a receipt for the same token. +// SessionCookie carries a browser's proof of a successful Web login. A +// top-level document load cannot set an Authorization header, so browser +// credentials are exchanged once for this HttpOnly receipt. const SessionCookie = "orchestra_session" -// SessionPath is the one Web-surface endpoint exempt from the token gate, -// because it *is* the token check: it verifies the Web token itself and -// exchanges it for a cookie. Gating it would make login unreachable. +// SessionPath is the one Web-surface endpoint exempt from the session gate, +// because it verifies login credentials and exchanges them for a cookie. const SessionPath = "/v1/ui/session" +// WebCredentials is the single configured browser operator identity. Only a +// bcrypt password hash is accepted; Orchestra has no self-service account +// creation or password-reset surface. +type WebCredentials struct { + Username string + PasswordHash string +} + +func (c WebCredentials) Validate() error { + if strings.TrimSpace(c.Username) == "" { + return fmt.Errorf("web username is required") + } + if c.PasswordHash == "" { + return fmt.Errorf("web password hash is required") + } + if _, err := bcrypt.Cost([]byte(c.PasswordHash)); err != nil { + return fmt.Errorf("web password hash must be bcrypt: %w", err) + } + return nil +} + +// Authenticate always performs bcrypt, even for an unknown username, so the +// response does not reveal whether the configured username was correct. +func (c WebCredentials) Authenticate(username, password string) bool { + passwordOK := bcrypt.CompareHashAndPassword([]byte(c.PasswordHash), []byte(password)) == nil + usernameOK := subtle.ConstantTimeCompare([]byte(username), []byte(c.Username)) == 1 + return passwordOK && usernameOK +} + // Sessions issues and validates those receipts. Values are random and stored // hashed, so a leaked snapshot of this map does not yield a usable cookie. type Sessions struct { @@ -161,9 +189,9 @@ func HTTP(tokens map[Surface]string, next http.Handler) http.Handler { return HTTPWithSessions(tokens, nil, next) } -// HTTPWithSessions additionally accepts a valid session cookie in place of a -// Bearer token, but only for the Web surface — every non-browser surface -// still has to present the token directly. +// HTTPWithSessions accepts a valid browser session cookie only for the Web +// surface. Supplying Sessions makes Web authentication mandatory even when +// the legacy Web bearer-token slot is empty. func HTTPWithSessions(tokens map[Surface]string, sessions *Sessions, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Federation has per-worker credentials, not one shared surface token. @@ -195,26 +223,24 @@ func HTTPWithSessions(tokens map[Surface]string, sessions *Sessions, next http.H if s == System { s = Web } - if expected := tokens[s]; expected != "" && subtle.ConstantTimeCompare([]byte(r.Header.Get("Authorization")), []byte("Bearer "+expected)) != 1 { + if s == Web && sessions != nil { ok := false - if s == Web && sessions != nil { - if c, err := r.Cookie(SessionCookie); err == nil { - ok = sessions.Valid(c.Value) - } - // The login endpoint authenticates itself, and the SPA shell - // must load before a browser can present anything. Static - // assets are not secrets; every /v1/ control path stays gated. - if r.URL.Path == SessionPath { - ok = true - } - if !strings.HasPrefix(r.URL.Path, "/v1/") && (r.Method == http.MethodGet || r.Method == http.MethodHead) { - ok = true - } + if c, err := r.Cookie(SessionCookie); err == nil { + ok = sessions.Valid(c.Value) + } + // The login endpoint authenticates itself, and the SPA shell must + // load before a browser can present a session. Static assets are not + // secrets; every other /v1/ control path remains session-gated. + if r.URL.Path == SessionPath || (!strings.HasPrefix(r.URL.Path, "/v1/") && (r.Method == http.MethodGet || r.Method == http.MethodHead)) { + ok = true } if !ok { http.Error(w, "unauthorized surface", http.StatusUnauthorized) return } + } else if expected := tokens[s]; expected != "" && subtle.ConstantTimeCompare([]byte(r.Header.Get("Authorization")), []byte("Bearer "+expected)) != 1 { + http.Error(w, "unauthorized surface", http.StatusUnauthorized) + return } if (s == Telegram || s == Ntfy) && r.Method != http.MethodGet && r.Method != http.MethodHead { http.Error(w, "notify-only surface", http.StatusForbidden) diff --git a/internal/authz/authz_test.go b/internal/authz/authz_test.go index ad0a638..da7f3cf 100644 --- a/internal/authz/authz_test.go +++ b/internal/authz/authz_test.go @@ -5,6 +5,8 @@ import ( "net/http/httptest" "testing" "time" + + "golang.org/x/crypto/bcrypt" ) func TestSurfaceCapabilities(t *testing.T) { @@ -51,7 +53,9 @@ func TestSystemSurfaceDowngradedByHTTPMiddleware(t *testing.T) { // B18: the web UI is a full control plane. A session cookie must be an // alternative *presentation* of the Web token, never a widening of it. func TestWebSessionCookieGatesControlPathsOnly(t *testing.T) { - tokens := map[Surface]string{Web: "secret"} + // An empty legacy Web bearer-token slot must not open the browser surface: + // providing Sessions means the caller needs a session cookie. + tokens := map[Surface]string{} sessions := &Sessions{} h := HTTPWithSessions(tokens, sessions, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) @@ -96,6 +100,28 @@ func TestWebSessionCookieGatesControlPathsOnly(t *testing.T) { } } +func TestWebCredentialsAuthenticate(t *testing.T) { + hash, err := bcrypt.GenerateFromPassword([]byte("correct horse battery staple"), bcrypt.MinCost) + if err != nil { + t.Fatal(err) + } + c := WebCredentials{Username: "operator", PasswordHash: string(hash)} + if err := c.Validate(); err != nil { + t.Fatalf("Validate: %v", err) + } + if !c.Authenticate("operator", "correct horse battery staple") { + t.Fatal("correct credentials rejected") + } + for _, attempt := range []struct{ username, password string }{{"operator", "wrong"}, {"other", "correct horse battery staple"}} { + if c.Authenticate(attempt.username, attempt.password) { + t.Fatalf("invalid credentials accepted: %+v", attempt) + } + } + if err := (WebCredentials{Username: "operator", PasswordHash: "not-a-bcrypt-hash"}).Validate(); err == nil { + t.Fatal("invalid bcrypt hash accepted") + } +} + func TestFederationRequestsUseTheirOwnCredentials(t *testing.T) { tokens := map[Surface]string{Web: "web-secret"} h := HTTPWithSessions(tokens, nil, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/internal/buildinfo/buildinfo.go b/internal/buildinfo/buildinfo.go new file mode 100644 index 0000000..2212b2b --- /dev/null +++ b/internal/buildinfo/buildinfo.go @@ -0,0 +1,19 @@ +// Package buildinfo exposes the provenance injected into Orchestra binaries. +// Build systems should set Revision, Time, and Dirty with -ldflags. Keeping +// the defaults explicit makes development binaries honest rather than +// pretending to be a deployable revision. +package buildinfo + +var ( + Revision = "devel" + Time = "unknown" + Dirty = "unknown" +) + +type Info struct { + Revision string `json:"revision"` + Time string `json:"time"` + Dirty string `json:"dirty"` +} + +func Current() Info { return Info{Revision: Revision, Time: Time, Dirty: Dirty} } diff --git a/internal/domain/domain.go b/internal/domain/domain.go index d1cef94..2aff8a4 100644 --- a/internal/domain/domain.go +++ b/internal/domain/domain.go @@ -22,11 +22,11 @@ var ErrInvalid = errors.New("invalid event") // task for this content; nothing was appended. var ErrDuplicate = errors.New("duplicate task ingestion") -// CurrentEventSchema is 2: schema 2 requires every event to declare its -// authorizing Surface (see ValidateEvent), enforced at the store append -// boundary. Schema 1 events already on disk replay unchanged — tolerant -// reader, not upcast (spec open question #2). -const CurrentEventSchema = 2 +// CurrentEventSchema is 3: schema 2 requires every event to declare its +// authorizing Surface; schema 3 adds lease fencing epochs. Older events stay +// readable so a deployment can recover its existing log before new writes +// are emitted (the store derives a non-renewable legacy epoch on replay). +const CurrentEventSchema = 3 type TaskState string @@ -102,8 +102,13 @@ type SessionEvidence struct { CheckedAt time.Time `json:"checked_at,omitempty"` } type Lease struct { - HarnessID string `json:"harness_id"` - Until time.Time `json:"until"` + HarnessID string `json:"harness_id"` + // Epoch is an opaque fencing token minted for every assignment. Versions + // change for ordinary lifecycle events; an epoch changes only when + // ownership changes, so an old pane can never become current again after + // a release/re-lease cycle. + Epoch string `json:"epoch"` + Until time.Time `json:"until"` } type Task struct { ID string `json:"id"` @@ -191,7 +196,18 @@ func ValidateEvent(e Event) error { if p == nil { return fmt.Errorf("%w: payload must be an object", ErrInvalid) } - return ValidatePayload(e.Type, p) + if err := ValidatePayload(e.Type, p); err != nil { + return err + } + if e.SchemaVersion >= 3 { + switch e.Type { + case "TaskLeased", "TaskLeaseRenewed", "TaskPickupValidated": + if v, ok := p["lease_epoch"].(string); !ok || strings.TrimSpace(v) == "" { + return fmt.Errorf("%w: lease_epoch required", ErrInvalid) + } + } + } + return nil } func ValidateCreated(p map[string]any) error { for _, k := range []string{"source", "external_id", "project"} { diff --git a/internal/federation/client.go b/internal/federation/client.go index 611e029..e0e9fe6 100644 --- a/internal/federation/client.go +++ b/internal/federation/client.go @@ -131,8 +131,8 @@ func (c Client) Heartbeat(ctx context.Context, health WorkerHealth) error { } return err } -func (c Client) Renew(ctx context.Context, taskID string, expectedVersion, ttlSeconds int) error { - resp, err := c.request(ctx, http.MethodPost, "/v1/federation/workers/"+url.PathEscape(c.WorkerID)+"/renew", map[string]any{"task_id": taskID, "expected_version": expectedVersion, "ttl_seconds": ttlSeconds}) +func (c Client) Renew(ctx context.Context, taskID, epoch string, expectedVersion, ttlSeconds int) error { + resp, err := c.request(ctx, http.MethodPost, "/v1/federation/workers/"+url.PathEscape(c.WorkerID)+"/renew", map[string]any{"task_id": taskID, "lease_epoch": epoch, "expected_version": expectedVersion, "ttl_seconds": ttlSeconds}) if resp != nil { resp.Body.Close() } @@ -176,22 +176,22 @@ func (c Client) PutArtifact(ctx context.Context, b []byte) (string, error) { } return out.Ref, nil } -func (c Client) Release(ctx context.Context, taskID, ref, anchor, transactionID string, expectedVersion int, evidence domain.SessionEvidence) error { - resp, err := c.request(ctx, http.MethodPost, "/v1/federation/workers/"+url.PathEscape(c.WorkerID)+"/handoff", map[string]any{"task_id": taskID, "handoff_ref": ref, "anchor_sha": anchor, "transaction_id": transactionID, "expected_version": expectedVersion, "session_evidence": evidence}) +func (c Client) Release(ctx context.Context, taskID, ref, anchor, transactionID, epoch string, expectedVersion int, evidence domain.SessionEvidence) error { + resp, err := c.request(ctx, http.MethodPost, "/v1/federation/workers/"+url.PathEscape(c.WorkerID)+"/handoff", map[string]any{"task_id": taskID, "handoff_ref": ref, "anchor_sha": anchor, "transaction_id": transactionID, "lease_epoch": epoch, "expected_version": expectedVersion, "session_evidence": evidence}) if resp != nil { resp.Body.Close() } return err } -func (c Client) Pickup(ctx context.Context, taskID, ref, anchor, transactionID string, leaseVersion int, evidence domain.SessionEvidence) error { - resp, err := c.request(ctx, http.MethodPost, "/v1/federation/workers/"+url.PathEscape(c.WorkerID)+"/pickup", map[string]any{"task_id": taskID, "handoff_ref": ref, "anchor_sha": anchor, "transaction_id": transactionID, "lease_version": leaseVersion, "session_evidence": evidence}) +func (c Client) Pickup(ctx context.Context, taskID, ref, anchor, transactionID, epoch string, leaseVersion int, evidence domain.SessionEvidence) error { + resp, err := c.request(ctx, http.MethodPost, "/v1/federation/workers/"+url.PathEscape(c.WorkerID)+"/pickup", map[string]any{"task_id": taskID, "handoff_ref": ref, "anchor_sha": anchor, "transaction_id": transactionID, "lease_epoch": epoch, "lease_version": leaseVersion, "session_evidence": evidence}) if resp != nil { resp.Body.Close() } return err } -func (c Client) Complete(ctx context.Context, taskID, reportRef, resultSHA, branch, remote string, expectedVersion int, receipt map[string]any, evidence domain.SessionEvidence) error { - resp, err := c.request(ctx, http.MethodPost, "/v1/federation/workers/"+url.PathEscape(c.WorkerID)+"/complete", map[string]any{"task_id": taskID, "handoff_ref": reportRef, "result_sha": resultSHA, "branch": branch, "remote": remote, "expected_version": expectedVersion, "receipt": receipt, "session_evidence": evidence}) +func (c Client) Complete(ctx context.Context, taskID, reportRef, resultSHA, branch, remote, epoch string, expectedVersion int, receipt map[string]any, evidence domain.SessionEvidence) error { + resp, err := c.request(ctx, http.MethodPost, "/v1/federation/workers/"+url.PathEscape(c.WorkerID)+"/complete", map[string]any{"task_id": taskID, "handoff_ref": reportRef, "result_sha": resultSHA, "branch": branch, "remote": remote, "lease_epoch": epoch, "expected_version": expectedVersion, "receipt": receipt, "session_evidence": evidence}) if resp != nil { resp.Body.Close() } diff --git a/internal/federation/federation.go b/internal/federation/federation.go index 42c82c7..f37d09b 100644 --- a/internal/federation/federation.go +++ b/internal/federation/federation.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "orchestra/internal/buildinfo" "os" "path/filepath" "sync" @@ -15,13 +16,15 @@ var ErrUnknownWorker = errors.New("unknown worker") var ErrUnauthorized = errors.New("worker authentication failed") type Worker struct { - ID string `json:"id"` - Address string `json:"address"` - Capacity int `json:"capacity"` - LastSeen time.Time `json:"last_seen"` - Online bool `json:"online"` - Health WorkerHealth `json:"health"` - Token string `json:"-"` + ID string `json:"id"` + Address string `json:"address"` + Capacity int `json:"capacity"` + SupportedProjects []string `json:"supported_projects"` + Build buildinfo.Info `json:"build"` + LastSeen time.Time `json:"last_seen"` + Online bool `json:"online"` + Health WorkerHealth `json:"health"` + Token string `json:"-"` } // WorkerHealth is reported by the worker that owns the local herdr socket. @@ -86,10 +89,12 @@ type persistedState struct { // is mode 0600, and retaining this binding prevents an arbitrary process from // registering a recovered worker ID and executing its pending approval. type persistedWorker struct { - ID string `json:"id"` - Address string `json:"address"` - Capacity int `json:"capacity"` - Token string `json:"token"` + ID string `json:"id"` + Address string `json:"address"` + Capacity int `json:"capacity"` + SupportedProjects []string `json:"supported_projects"` + Build buildinfo.Info `json:"build"` + Token string `json:"token"` } // Load restores durable capture/command state. Call this before accepting @@ -123,7 +128,7 @@ func (r *Registry) Load() error { if id == "" || w.ID != id || w.Token == "" { return fmt.Errorf("invalid federation worker %q", id) } - r.workers[id] = Worker{ID: w.ID, Address: w.Address, Capacity: w.Capacity, Token: w.Token} + r.workers[id] = Worker{ID: w.ID, Address: w.Address, Capacity: w.Capacity, SupportedProjects: w.SupportedProjects, Build: w.Build, Token: w.Token} } return nil } @@ -135,7 +140,7 @@ func (r *Registry) persistLocked() error { } workers := make(map[string]persistedWorker, len(r.workers)) for id, w := range r.workers { - workers[id] = persistedWorker{ID: w.ID, Address: w.Address, Capacity: w.Capacity, Token: w.Token} + workers[id] = persistedWorker{ID: w.ID, Address: w.Address, Capacity: w.Capacity, SupportedProjects: w.SupportedProjects, Build: w.Build, Token: w.Token} } b, err := json.Marshal(persistedState{Captures: r.captures, Commands: r.commands, Workers: workers}) if err != nil { @@ -145,12 +150,31 @@ func (r *Registry) persistLocked() error { return err } tmp := r.StatePath + ".tmp" - if err := os.WriteFile(tmp, b, 0600); err != nil { + f, err := os.OpenFile(tmp, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + if err != nil { + return err + } + if _, err = f.Write(b); err == nil { + err = f.Sync() + } + if closeErr := f.Close(); err == nil { + err = closeErr + } + if err != nil { + _ = os.Remove(tmp) return err } if err := os.Rename(tmp, r.StatePath); err != nil { return err } + dir, err := os.Open(filepath.Dir(r.StatePath)) + if err != nil { + return err + } + defer dir.Close() + if err := dir.Sync(); err != nil { + return err + } return os.Chmod(r.StatePath, 0600) } @@ -396,10 +420,32 @@ func (r *Registry) Available(id string) bool { if !ok { return false } - w.Online = time.Since(w.LastSeen) <= r.TTL + // A heartbeat merely proves the worker process can reach the coordinator. + // Lease admission additionally requires a fresh probe of the worker's + // local herdr; otherwise a partitioned/down herdr still attracts work. + w.Online = time.Since(w.LastSeen) <= r.TTL && w.Health.HerdrStatus == "reachable" && !w.Health.CheckedAt.IsZero() && time.Since(w.Health.CheckedAt) <= r.TTL r.workers[id] = w return w.Online } + +// Supports reports whether an online worker explicitly declared the project. +// An omitted declaration is deliberately not treated as a wildcard: workers +// must never receive a project for which they have no local checkout. +func (r *Registry) Supports(id, project string) bool { + r.mu.Lock() + defer r.mu.Unlock() + r.init() + w, ok := r.workers[id] + if !ok || time.Since(w.LastSeen) > r.TTL { + return false + } + for _, candidate := range w.SupportedProjects { + if candidate == project { + return true + } + } + return false +} func (r *Registry) Snapshot() []Worker { r.mu.Lock() defer r.mu.Unlock() diff --git a/internal/federation/federation_test.go b/internal/federation/federation_test.go index d289e6e..53f81ce 100644 --- a/internal/federation/federation_test.go +++ b/internal/federation/federation_test.go @@ -29,6 +29,49 @@ func TestCursorIsMonotonicAndAuthenticationIsRequired(t *testing.T) { } } +func TestSupportedProjectsPersistAndGateAvailability(t *testing.T) { + path := filepath.Join(t.TempDir(), "state.json") + r := &Registry{StatePath: path} + if err := r.Register(Worker{ID: "w", Token: "t", SupportedProjects: []string{"test-e2e"}}, ""); err != nil { + t.Fatal(err) + } + if !r.Supports("w", "test-e2e") || r.Supports("w", "correx") { + t.Fatalf("unexpected project support") + } + restarted := &Registry{StatePath: path} + if err := restarted.Load(); err != nil { + t.Fatal(err) + } + if err := restarted.Register(Worker{ID: "w", Token: "t", SupportedProjects: []string{"test-e2e"}}, ""); err != nil { + t.Fatal(err) + } + if !restarted.Supports("w", "test-e2e") || restarted.Supports("w", "correx") { + t.Fatalf("project support did not survive restart") + } +} + +func TestAvailableRequiresFreshReachableLocalHerdrHealth(t *testing.T) { + r := &Registry{} + if err := r.Register(Worker{ID: "w", Token: "t"}, ""); err != nil { + t.Fatal(err) + } + if r.Available("w") { + t.Fatal("registration without local herdr probe admitted a worker") + } + if err := r.Heartbeat("w", WorkerHealth{HerdrStatus: "reachable", CheckedAt: time.Now().UTC()}); err != nil { + t.Fatal(err) + } + if !r.Available("w") { + t.Fatal("fresh reachable local herdr was not admitted") + } + if err := r.Heartbeat("w", WorkerHealth{HerdrStatus: "unreachable", CheckedAt: time.Now().UTC()}); err != nil { + t.Fatal(err) + } + if r.Available("w") { + t.Fatal("unreachable local herdr was admitted") + } +} + func TestPendingApprovalSurvivesRegistryRestart(t *testing.T) { path := filepath.Join(t.TempDir(), "federation-state.json") r := &Registry{StatePath: path} diff --git a/internal/integration/end_to_end_test.go b/internal/integration/end_to_end_test.go index a07b6d0..9b1479c 100644 --- a/internal/integration/end_to_end_test.go +++ b/internal/integration/end_to_end_test.go @@ -129,9 +129,12 @@ func TestEndToEndIngestRouteLeaseRotateAndComplete(t *testing.T) { } got, _ := s.Task(task.ID) if got.State == domain.StateLeased && h.releases == 1 { - if err := s.Append(domain.Event{ID: domain.NewID(), Type: "TaskReleased", TaskID: task.ID, Version: got.Version + 1, Surface: string(authz.System), Payload: mustJSON(map[string]string{ - "handoff_ref": h.ref, - "anchor_sha": "0123456789012345678901234567890123456789", + if err := s.Append(domain.Event{ID: domain.NewID(), Type: "TaskReleased", TaskID: task.ID, Version: got.Version + 1, Surface: string(authz.System), Payload: mustJSON(map[string]any{ + "handoff_ref": h.ref, + "anchor_sha": "0123456789012345678901234567890123456789", + "harness_id": got.Lease.HarnessID, + "lease_epoch": got.Lease.Epoch, + "expected_version": got.Version, })}); err != nil { t.Fatal(err) } @@ -186,9 +189,13 @@ func TestRestartReplayAndReconcileKillsOrphan(t *testing.T) { } // A fresh coordinator sees the durable session, then drops it once the lease is gone. ref, _ := s.PutArtifact([]byte("handoff")) - if err := s.Append(domain.Event{ID: domain.NewID(), Type: "TaskReleased", TaskID: task.ID, Version: 3, Surface: string(authz.System), Payload: mustJSON(map[string]string{ - "handoff_ref": ref, - "anchor_sha": "0123456789012345678901234567890123456789", + leased, _ := s.Task(task.ID) + if err := s.Append(domain.Event{ID: domain.NewID(), Type: "TaskReleased", TaskID: task.ID, Version: 3, Surface: string(authz.System), Payload: mustJSON(map[string]any{ + "handoff_ref": ref, + "anchor_sha": "0123456789012345678901234567890123456789", + "harness_id": leased.Lease.HarnessID, + "lease_epoch": leased.Lease.Epoch, + "expected_version": leased.Version, })}); err != nil { t.Fatal(err) } @@ -230,9 +237,13 @@ func TestProviderRetryReflectionQuotaAndVersionConflict(t *testing.T) { t.Fatal(err) } reflector := &fakeReflector{} + leased, _ := s.Task(task.ID) if err := (provider.ReflectingSink{Sink: s, Tasks: s, Reflector: reflector}).Append(domain.Event{ID: domain.NewID(), Type: "TaskCompleted", TaskID: task.ID, Version: 3, Surface: string(authz.System), Payload: mustJSON(map[string]any{ - "report_ref": ref, - "receipt": map[string]any{"harness_id": "h1", "consumed": 1}, + "report_ref": ref, + "receipt": map[string]any{"harness_id": "h1", "consumed": 1}, + "harness_id": leased.Lease.HarnessID, + "lease_epoch": leased.Lease.Epoch, + "expected_version": leased.Version, })}); err != nil { t.Fatal(err) } diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index 5fa7a80..0ad3221 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -6,6 +6,7 @@ package orchestrator import ( "context" "encoding/json" + "errors" "fmt" "orchestra/internal/authz" "orchestra/internal/continuity" @@ -412,10 +413,29 @@ func (c *Coordinator) saveSessionsLocked() error { return err } tmp := c.StatePath + ".tmp" - if err = os.WriteFile(tmp, b, 0600); err != nil { + f, err := os.OpenFile(tmp, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + if err != nil { return err } - return os.Rename(tmp, c.StatePath) + if _, err = f.Write(b); err == nil { + err = f.Sync() + } + if closeErr := f.Close(); err == nil { + err = closeErr + } + if err != nil { + _ = os.Remove(tmp) + return err + } + if err = os.Rename(tmp, c.StatePath); err != nil { + return err + } + dir, err := os.Open(filepath.Dir(c.StatePath)) + if err != nil { + return err + } + defer dir.Close() + return dir.Sync() } // Reconcile drops mappings whose task lease did not survive restart and kills @@ -573,7 +593,7 @@ func (c *Coordinator) expire(ctx context.Context) ([]domain.Event, error) { if a, ae := c.adapterFor(taskID, s); ae == nil { if p, ok := a.(herdr.PaneExit); ok { if exited, ee := p.PaneExited(ctx, s); ee == nil && exited { - b, _ := json.Marshal(map[string]string{"reason": "pane_exited", "harness_id": s.Harness}) + b, _ := json.Marshal(map[string]any{"reason": "pane_exited", "harness_id": s.Harness, "lease_epoch": t.Lease.Epoch, "expected_version": t.Version}) _ = c.Store.Append(domain.Event{ID: domain.NewID(), Type: "TaskReleased", TaskID: taskID, Version: t.Version + 1, Payload: b, Surface: string(authz.System)}) } } @@ -581,24 +601,47 @@ func (c *Coordinator) expire(ctx context.Context) ([]domain.Event, error) { } } c.mu.Unlock() - events, err := c.Store.ExpireLeases(time.Now()) - if err != nil { - return events, err - } - for _, e := range events { - c.loadSessions() - c.mu.Lock() - s, ok := c.sessions[e.TaskID] - delete(c.sessions, e.TaskID) - if ok { - if a, ae := c.adapterFor(e.TaskID, s); ae == nil { - _ = a.Kill(ctx, s) - } + var events []domain.Event + var firstErr error + for _, task := range c.Store.Tasks() { + if task.State != domain.StateLeased || task.Lease == nil || task.Lease.Until.After(time.Now()) { + continue } - _ = c.saveSessionsLocked() + // Stop a local predecessor before making its lease eligible for a + // successor. If this cannot be done, keep both the mapping and the + // lease: safety beats reclaim speed. + c.mu.Lock() + s, local := c.sessions[task.ID] c.mu.Unlock() + if local { + a, adapterErr := c.adapterFor(task.ID, s) + if adapterErr != nil { + if firstErr == nil { + firstErr = fmt.Errorf("expire %s: resolve old pane: %w", task.ID, adapterErr) + } + continue + } + if killErr := a.Kill(ctx, s); killErr != nil { + if firstErr == nil { + firstErr = fmt.Errorf("expire %s: quarantine old pane: %w", task.ID, killErr) + } + continue + } + c.mu.Lock() + delete(c.sessions, task.ID) + _ = c.saveSessionsLocked() + c.mu.Unlock() + } + e, expireErr := c.Store.ExpireLease(task.ID, time.Now()) + if expireErr != nil { + if !errors.Is(expireErr, domain.ErrConflict) && firstErr == nil { + firstErr = expireErr + } + continue + } + events = append(events, e) } - return events, nil + return events, firstErr } // handoffReason reads HandoffFile from the worktree, if present, and returns @@ -722,13 +765,17 @@ func (c *Coordinator) rotate(ctx context.Context, hard float64) { // tick or TTL expiry to reclaim. continue } - b, _ := json.Marshal(map[string]string{"handoff_ref": ref, "reason": reason, "anchor_sha": anchorSHA}) + b, _ := json.Marshal(map[string]any{"handoff_ref": ref, "reason": reason, "anchor_sha": anchorSHA, "harness_id": task.Lease.HarnessID, "lease_epoch": task.Lease.Epoch, "expected_version": task.Version}) e := domain.Event{ID: domain.NewID(), Type: "TaskReleased", TaskID: taskID, Version: task.Version + 1, Payload: b, Surface: string(authz.System)} if c.Store.Append(e) == nil { - c.mu.Lock() - delete(c.sessions, taskID) - _ = c.saveSessionsLocked() - c.mu.Unlock() + // A release only transfers the lease; this local coordinator owns + // the predecessor pane until it has actually stopped it. + if err := a.Kill(ctx, session); err == nil { + c.mu.Lock() + delete(c.sessions, taskID) + _ = c.saveSessionsLocked() + c.mu.Unlock() + } } } } @@ -842,11 +889,14 @@ func (c *Coordinator) finishRelease(ctx context.Context, taskID string, task dom // invalid TaskReleased payload, same as rotate()'s bare continue. return TurnRefuse, nil } - b, _ := json.Marshal(map[string]string{"handoff_ref": ref, "reason": reason, "anchor_sha": anchorSHA}) + b, _ := json.Marshal(map[string]any{"handoff_ref": ref, "reason": reason, "anchor_sha": anchorSHA, "harness_id": task.Lease.HarnessID, "lease_epoch": task.Lease.Epoch, "expected_version": task.Version}) e := domain.Event{ID: domain.NewID(), Type: "TaskReleased", TaskID: taskID, Version: task.Version + 1, Payload: b, Surface: string(authz.System)} if err := c.Store.Append(e); err != nil { return TurnRefuse, nil } + if err := a.Kill(ctx, session); err != nil { + return TurnRefuse, nil + } c.mu.Lock() delete(c.sessions, taskID) _ = c.saveSessionsLocked() @@ -978,6 +1028,12 @@ func (c *Coordinator) block(t domain.Task, reason string) error { p["session_evidence"] = domain.SessionEvidence{PaneID: s.PaneID, HarnessID: s.HerdrID, PaneState: "open", Source: "coordinator", CheckedAt: time.Now().UTC()} } b, _ := json.Marshal(p) + if t.Lease != nil { + p["harness_id"] = t.Lease.HarnessID + p["lease_epoch"] = t.Lease.Epoch + p["expected_version"] = t.Version + b, _ = json.Marshal(p) + } return c.Store.Append(domain.Event{ID: domain.NewID(), Type: "TaskBlocked", TaskID: t.ID, Version: t.Version + 1, Payload: b, Surface: string(authz.System)}) } diff --git a/internal/orchestrator/worktrees_test.go b/internal/orchestrator/worktrees_test.go index 82e333f..3344628 100644 --- a/internal/orchestrator/worktrees_test.go +++ b/internal/orchestrator/worktrees_test.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "testing" ) @@ -73,7 +74,7 @@ func TestGitWorktreesCommitsTaskFile(t *testing.T) { initRepo(t, repo) w := orchestrator.GitWorktrees{Root: filepath.Join(base, "wt"), Repo: repo} - task := domain.Task{ID: "t1", Project: "p", Source: "jsonl", ExternalID: "1", Title: "do the thing"} + task := domain.Task{ID: "t1", Project: "p", Source: "jsonl", ExternalID: "1", Title: "do the thing", Acceptance: []string{"tests pass"}, QualityGate: "go test ./..."} path, err := w.Create(context.Background(), task) if err != nil { @@ -88,6 +89,9 @@ func TestGitWorktreesCommitsTaskFile(t *testing.T) { if string(got) != string(want) { t.Fatalf("TASK.md content mismatch:\ngot: %s\nwant: %s", got, want) } + if !strings.Contains(string(got), "## Acceptance criteria") || !strings.Contains(string(got), ".orchestra/done") { + t.Fatalf("TASK.md is missing deterministic completion contract: %s", got) + } status, err := exec.Command("git", "-C", path, "status", "--porcelain", "--", "TASK.md").Output() if err != nil { diff --git a/internal/registry/registry.go b/internal/registry/registry.go index 8798cc0..5349c09 100644 --- a/internal/registry/registry.go +++ b/internal/registry/registry.go @@ -28,6 +28,7 @@ type Project struct { // wires (single-repo deployments keep working unchanged). Repo string `json:"repo,omitempty"` WorktreeRoot string `json:"worktree_root,omitempty"` + QualityGate string `json:"quality_gate,omitempty"` } type Machine struct { ID string `json:"id"` diff --git a/internal/router/router.go b/internal/router/router.go index 39a74c4..7b8e840 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -14,6 +14,12 @@ import ( ) type Availability interface{ Available(h registry.Herdr) bool } + +// ProjectAvailability is an optional stricter availability contract used by +// federated workers, whose local checkout configuration is authoritative. +type ProjectAvailability interface { + Supports(h registry.Herdr, project string) bool +} type AlwaysAvailable struct{} func (AlwaysAvailable) Available(registry.Herdr) bool { return true } @@ -170,7 +176,11 @@ func (r *Router) AssignPending() ([]domain.Event, error) { continue } for _, h := range cs { - if !matches(t.Capability, h.Capabilities) || !r.Availability.Available(h) || occupied(r.Store, h.ID, h.Concurrency) { + projectOK := true + if projects, ok := r.Availability.(ProjectAvailability); ok { + projectOK = projects.Supports(h, t.Project) + } + if !projectOK || !matches(t.Capability, h.Capabilities) || !r.Availability.Available(h) || occupied(r.Store, h.ID, h.Concurrency) { continue } e, err := r.Store.Lease(t.ID, h.ID, 30*time.Minute) diff --git a/internal/router/router_test.go b/internal/router/router_test.go index 0e1b30d..f382ab9 100644 --- a/internal/router/router_test.go +++ b/internal/router/router_test.go @@ -14,6 +14,13 @@ type reachable struct{} func (reachable) Reachable(string, time.Duration) bool { return true } +type projectAvailability struct{ projects map[string]bool } + +func (p projectAvailability) Available(registry.Herdr) bool { return true } +func (p projectAvailability) Supports(h registry.Herdr, project string) bool { + return p.projects[h.ID+"/"+project] +} + func TestAssignsByAffinityCapabilityAndConcurrency(t *testing.T) { s, err := store.Open(t.TempDir()) if err != nil { @@ -56,6 +63,32 @@ func TestAssignsByAffinityCapabilityAndConcurrency(t *testing.T) { } } +func TestAssignPendingRequiresWorkerProjectSupport(t *testing.T) { + s, err := store.Open(t.TempDir()) + if err != nil { + t.Fatal(err) + } + r, err := registry.New(registry.Config{Projects: []registry.Project{{ID: "p", MachineAffinity: []string{"m"}}}, Machines: []registry.Machine{{ID: "m", Address: "unused"}}, Herdrs: []registry.Herdr{{ID: "h", MachineID: "m", Concurrency: 1}}}) + if err != nil { + t.Fatal(err) + } + b, _ := json.Marshal(map[string]any{"source": "test", "external_id": "project-check", "project": "p"}) + if err := s.Append(domain.Event{ID: "create", TaskID: "t", Type: "TaskCreated", Version: 1, Payload: b, Surface: string(authz.System)}); err != nil { + t.Fatal(err) + } + rt := Router{Store: s, Registry: r, Reachability: reachable{}, Availability: projectAvailability{projects: map[string]bool{}}} + if got, err := rt.AssignPending(); err != nil || len(got) != 0 { + t.Fatalf("unsupported project lease = %#v, %v", got, err) + } + if got, _ := s.Task("t"); got.State != domain.StateQueued { + t.Fatalf("unsupported project state=%s", got.State) + } + rt.Availability = projectAvailability{projects: map[string]bool{"h/p": true}} + if got, err := rt.AssignPending(); err != nil || len(got) != 1 { + t.Fatalf("supported project lease = %#v, %v", got, err) + } +} + // TestRotationDoesNotCountAgainstRetryLimit guards B4: rotation is // TaskReleased carrying a valid handoff_ref (spec §5.3: "rotation = // intra-task lease transfer"), never a failure. A task healthy enough to @@ -103,10 +136,13 @@ func TestRotationDoesNotCountAgainstRetryLimit(t *testing.T) { } continue } - rb, _ := json.Marshal(map[string]string{ - "handoff_ref": handoffRef, - "reason": "threshold", - "anchor_sha": "0123456789abcdef0123456789abcdef01234567", + rb, _ := json.Marshal(map[string]any{ + "handoff_ref": handoffRef, + "reason": "threshold", + "anchor_sha": "0123456789abcdef0123456789abcdef01234567", + "harness_id": task.Lease.HarnessID, + "lease_epoch": task.Lease.Epoch, + "expected_version": task.Version, }) release := domain.Event{ID: domain.NewID(), Type: "TaskReleased", TaskID: "a", Version: task.Version + 1, Payload: rb, Surface: string(authz.System)} if err := s.Append(release); err != nil { diff --git a/internal/store/store.go b/internal/store/store.go index d95c3b0..69e9d43 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -32,27 +32,10 @@ func Open(dir string) (*Store, error) { if err := os.MkdirAll(s.cas, 0755); err != nil { return nil, err } - var snapshotSeq uint64 - if b, readErr := os.ReadFile(s.snapshot); readErr == nil { - var snap struct { - Seq uint64 `json:"seq"` - Tasks []domain.Task `json:"tasks"` - } - if json.Unmarshal(b, &snap) != nil { - return nil, fmt.Errorf("invalid snapshot") - } - for _, t := range snap.Tasks { - s.tasks[t.ID] = t - s.external[t.Source+"\x00"+t.ExternalID] = t.ID - } - snapshotSeq = snap.Seq - // Continue event numbering after the snapshot. Without restoring this - // cursor, the first append after a restart reused sequence 1 and made - // the append-only log unreplayable. - s.seq = snapshotSeq - } else if !errors.Is(readErr, os.ErrNotExist) { - return nil, readErr - } + // A snapshot is a disposable read cache, never recovery authority. Loading + // it before the log let a partially-written snapshot become a different + // history than events.jsonl after a crash. Rebuild every projection from + // the append-only, fsynced log instead. f, err := os.Open(s.path) if os.IsNotExist(err) { return s, nil @@ -62,16 +45,13 @@ func Open(dir string) (*Store, error) { } defer f.Close() sc := bufio.NewScanner(f) - var expected uint64 = snapshotSeq + 1 + var expected uint64 = 1 for sc.Scan() { var e domain.Event if err := json.Unmarshal(sc.Bytes(), &e); err == nil { if err := domain.ValidateEvent(e); err != nil { return nil, err } - if e.Seq < expected { - continue - } if e.Seq != expected { return nil, fmt.Errorf("event sequence gap: got %d, want %d", e.Seq, expected) } @@ -151,9 +131,20 @@ func (s *Store) apply(e domain.Event) error { s.external[t.Source+"\x00"+t.ExternalID] = t.ID case "TaskLeased": t.State = domain.StateLeased - t.Lease = &domain.Lease{HarnessID: p["harness_id"].(string), Until: time.Unix(0, int64(p["until_ns"].(float64)))} + epoch, _ := p["lease_epoch"].(string) + if epoch == "" { + // A pre-fencing event cannot safely be renewed by an old worker. + // Deriving a stable token from the durable event identity makes the + // recovered lease observable but non-renewable until it expires. + epoch = "legacy:" + e.ID + } + t.Lease = &domain.Lease{HarnessID: p["harness_id"].(string), Epoch: epoch, Until: time.Unix(0, int64(p["until_ns"].(float64)))} case "TaskLeaseRenewed": - t.Lease = &domain.Lease{HarnessID: p["harness_id"].(string), Until: time.Unix(0, int64(p["until_ns"].(float64)))} + epoch, _ := p["lease_epoch"].(string) + if epoch == "" && t.Lease != nil { + epoch = t.Lease.Epoch + } + t.Lease = &domain.Lease{HarnessID: p["harness_id"].(string), Epoch: epoch, Until: time.Unix(0, int64(p["until_ns"].(float64)))} case "TaskReleased": t.State = domain.StateQueued t.Lease = nil @@ -292,6 +283,9 @@ func (s *Store) Append(e domain.Event) error { return domain.ErrConflict } } + if err := s.validateTransition(e, t, taskExists, contract); err != nil { + return err + } if e.Type == "TaskLeased" { var p struct { ExpectedVersion *int `json:"expected_version"` @@ -333,9 +327,6 @@ func (s *Store) Append(e domain.Event) error { } } } - if err := s.apply(e); err != nil { - return err - } f, err := os.OpenFile(s.path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return err @@ -348,10 +339,60 @@ func (s *Store) Append(e domain.Event) error { if err = f.Sync(); err != nil { return err } + // The event is the commit record. Do not expose a projection that cannot + // be recovered from it after a power loss. + if err := s.apply(e); err != nil { + return err + } s.events = append(s.events, e) s.seq = e.Seq - if err := s.writeSnapshot(); err != nil { - return err + // Snapshot failure does not roll back a committed event. Open always + // rebuilds from the log, so leaving a stale cache is safe. + _ = s.writeSnapshot() + return nil +} + +// validateTransition keeps lifecycle authority at the durable append +// boundary. A task may be completed/failed while queued by an external +// provider, but once a lease exists its owner and fencing epoch are required +// for every lifecycle mutation. +func (s *Store) validateTransition(e domain.Event, t domain.Task, exists bool, p map[string]any) error { + if !exists { + if e.Type != "TaskCreated" && e.Type != "QuotaReported" && e.Type != "StandupAdvisory" && e.Type != "ApprovalGranted" && e.Type != "ApprovalDenied" { + return domain.ErrNotFound + } + return nil + } + if e.Type == "TaskLeased" && t.State != domain.StateQueued { + return domain.ErrConflict + } + if t.State != domain.StateLeased || t.Lease == nil { + return nil + } + switch e.Type { + case "TaskLeaseRenewed", "TaskReleased", "TaskPickupValidated", "TaskCompleted", "TaskBlocked", "TaskFailed": + owner, _ := p["harness_id"].(string) + epoch, _ := p["lease_epoch"].(string) + // Expiry is the one coordinator-owned relinquish path. It still binds + // the exact epoch that was observed when the timer fired. + if e.Type == "TaskReleased" { + if reason, _ := p["reason"].(string); (reason == "lease_expired" || reason == "pane_exited") && owner == t.Lease.HarnessID && epoch == t.Lease.Epoch { + return nil + } + } + if owner != t.Lease.HarnessID || epoch == "" || epoch != t.Lease.Epoch { + return domain.ErrConflict + } + case "TaskCorrected": + // Corrections may repair metadata while a task is leased, but cannot + // smuggle in a lifecycle transition around the current fenced owner. + if _, changesState := p["state"]; changesState { + owner, _ := p["harness_id"].(string) + epoch, _ := p["lease_epoch"].(string) + if owner != t.Lease.HarnessID || epoch == "" || epoch != t.Lease.Epoch { + return domain.ErrConflict + } + } } return nil } @@ -368,10 +409,28 @@ func (s *Store) writeSnapshot() error { return err } tmp := s.snapshot + ".tmp" - if err = os.WriteFile(tmp, b, 0644); err != nil { + f, err := os.OpenFile(tmp, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if err != nil { return err } - return os.Rename(tmp, s.snapshot) + if _, err = f.Write(b); err == nil { + err = f.Sync() + } + if closeErr := f.Close(); err == nil { + err = closeErr + } + if err != nil { + return err + } + if err = os.Rename(tmp, s.snapshot); err != nil { + return err + } + dir, err := os.Open(filepath.Dir(s.snapshot)) + if err != nil { + return err + } + defer dir.Close() + return dir.Sync() } func (s *Store) Tasks() []domain.Task { s.mu.Lock() @@ -397,9 +456,39 @@ func (s *Store) PutArtifact(b []byte) (string, error) { h := domain.Hash(b) p := filepath.Join(s.cas, h) if _, err := os.Stat(p); errors.Is(err, os.ErrNotExist) { - if err = os.WriteFile(p, b, 0644); err != nil { + tmp := p + ".tmp-" + domain.NewID() + f, openErr := os.OpenFile(tmp, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644) + if openErr != nil { + return "", openErr + } + if _, err = f.Write(b); err == nil { + err = f.Sync() + } + if closeErr := f.Close(); err == nil { + err = closeErr + } + if err != nil { + _ = os.Remove(tmp) return "", err } + if err = os.Rename(tmp, p); err != nil && !errors.Is(err, os.ErrExist) { + _ = os.Remove(tmp) + return "", err + } + if !errors.Is(err, os.ErrExist) { + dir, openErr := os.Open(s.cas) + if openErr != nil { + return "", openErr + } + syncErr := dir.Sync() + closeErr := dir.Close() + if syncErr != nil { + return "", syncErr + } + if closeErr != nil { + return "", closeErr + } + } } return h, nil } @@ -450,7 +539,7 @@ func (s *Store) Lease(id, harness string, ttl time.Duration) (domain.Event, erro if t.State != domain.StateQueued { return domain.Event{}, domain.ErrConflict } - payload := map[string]any{"harness_id": harness, "ttl": ttl.Seconds(), "until_ns": time.Now().Add(ttl).UnixNano(), "expected_version": t.Version} + payload := map[string]any{"harness_id": harness, "lease_epoch": domain.NewID(), "ttl": ttl.Seconds(), "until_ns": time.Now().Add(ttl).UnixNano(), "expected_version": t.Version} if t.HandoffRef != "" { payload["handoff_ref"] = t.HandoffRef payload["transaction_id"] = t.ReleaseTransaction @@ -464,7 +553,7 @@ func (s *Store) Lease(id, harness string, ttl time.Duration) (domain.Event, erro // RenewLease atomically extends the current owner's lease. The observed task // version is part of the request so an old worker can never renew a lease // after release/reassignment. -func (s *Store) RenewLease(id, harness string, expectedVersion int, ttl time.Duration) (domain.Event, error) { +func (s *Store) RenewLease(id, harness, epoch string, expectedVersion int, ttl time.Duration) (domain.Event, error) { if ttl <= 0 { return domain.Event{}, fmt.Errorf("%w: ttl must be positive", domain.ErrInvalid) } @@ -472,10 +561,10 @@ func (s *Store) RenewLease(id, harness string, expectedVersion int, ttl time.Dur if !ok { return domain.Event{}, domain.ErrNotFound } - if t.State != domain.StateLeased || t.Lease == nil || t.Lease.HarnessID != harness || t.Version != expectedVersion { + if t.State != domain.StateLeased || t.Lease == nil || t.Lease.HarnessID != harness || t.Lease.Epoch != epoch || t.Version != expectedVersion { return domain.Event{}, domain.ErrConflict } - p, _ := json.Marshal(map[string]any{"harness_id": harness, "until_ns": time.Now().Add(ttl).UnixNano(), "expected_version": expectedVersion}) + p, _ := json.Marshal(map[string]any{"harness_id": harness, "lease_epoch": epoch, "until_ns": time.Now().Add(ttl).UnixNano(), "expected_version": expectedVersion}) e := domain.Event{ID: domain.NewID(), Type: "TaskLeaseRenewed", TaskID: id, Version: t.Version + 1, Payload: p, Surface: string(authz.System)} return e, s.Append(e) } @@ -483,14 +572,30 @@ func (s *Store) RenewLease(id, harness string, expectedVersion int, ttl time.Dur func (s *Store) ExpireLeases(now time.Time) ([]domain.Event, error) { var out []domain.Event for _, t := range s.Tasks() { - if t.State == domain.StateLeased && t.Lease != nil && !t.Lease.Until.After(now) { - p, _ := json.Marshal(map[string]any{"reason": "lease_expired", "harness_id": t.Lease.HarnessID}) - e := domain.Event{ID: domain.NewID(), Type: "TaskReleased", TaskID: t.ID, Version: t.Version + 1, Payload: p, Surface: string(authz.System)} - if err := s.Append(e); err != nil { + if e, err := s.ExpireLease(t.ID, now); err != nil { + if !errors.Is(err, domain.ErrConflict) { return out, err } + } else if e.ID != "" { out = append(out, e) } } return out, nil } + +// ExpireLease releases exactly the observed lease if, and only if, its TTL +// has elapsed. Coordinators use this one-task form to stop their local pane +// before publishing the release event; the batch helper remains for +// deployments without a local coordinator. +func (s *Store) ExpireLease(id string, now time.Time) (domain.Event, error) { + t, ok := s.Task(id) + if !ok { + return domain.Event{}, domain.ErrNotFound + } + if t.State != domain.StateLeased || t.Lease == nil || t.Lease.Until.After(now) { + return domain.Event{}, domain.ErrConflict + } + p, _ := json.Marshal(map[string]any{"reason": "lease_expired", "harness_id": t.Lease.HarnessID, "lease_epoch": t.Lease.Epoch, "expected_version": t.Version}) + e := domain.Event{ID: domain.NewID(), Type: "TaskReleased", TaskID: t.ID, Version: t.Version + 1, Payload: p, Surface: string(authz.System)} + return e, s.Append(e) +} diff --git a/internal/store/store_test.go b/internal/store/store_test.go index 7c0d8c7..0f27be2 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -3,6 +3,7 @@ package store import ( "encoding/json" "errors" + "fmt" "os" "path/filepath" "strings" @@ -35,7 +36,7 @@ func TestLeaseCarriesReleasedHandoffRef(t *testing.T) { t.Fatal(err) } task, _ := s.Task("t") - p, _ := json.Marshal(map[string]string{"handoff_ref": ref, "anchor_sha": "0123456789012345678901234567890123456789"}) + p, _ := json.Marshal(map[string]any{"handoff_ref": ref, "anchor_sha": "0123456789012345678901234567890123456789", "harness_id": task.Lease.HarnessID, "lease_epoch": task.Lease.Epoch, "expected_version": task.Version}) if err := s.Append(domain.Event{ID: "release", Type: "TaskReleased", TaskID: "t", Version: task.Version + 1, Payload: p, Surface: string(authz.System)}); err != nil { t.Fatal(err) } @@ -64,13 +65,13 @@ func TestRenewLeaseRequiresCurrentOwnerAndVersion(t *testing.T) { t.Fatal(err) } before, _ := s.Task("t") - if _, err := s.RenewLease("t", "worker-b", before.Version, time.Hour); !errors.Is(err, domain.ErrConflict) { + if _, err := s.RenewLease("t", "worker-b", before.Lease.Epoch, before.Version, time.Hour); !errors.Is(err, domain.ErrConflict) { t.Fatalf("other worker renewal = %v, want conflict", err) } - if _, err := s.RenewLease("t", "worker-a", before.Version-1, time.Hour); !errors.Is(err, domain.ErrConflict) { + if _, err := s.RenewLease("t", "worker-a", before.Lease.Epoch, before.Version-1, time.Hour); !errors.Is(err, domain.ErrConflict) { t.Fatalf("stale renewal = %v, want conflict", err) } - e, err := s.RenewLease("t", "worker-a", before.Version, time.Hour) + e, err := s.RenewLease("t", "worker-a", before.Lease.Epoch, before.Version, time.Hour) if err != nil { t.Fatal(err) } @@ -78,11 +79,107 @@ func TestRenewLeaseRequiresCurrentOwnerAndVersion(t *testing.T) { if e.Type != "TaskLeaseRenewed" || after.Version != before.Version+1 || after.Lease == nil || !after.Lease.Until.After(before.Lease.Until) { t.Fatalf("renewal was not projected: before=%+v after=%+v event=%+v", before, after, e) } - if _, err := s.RenewLease("t", "worker-a", before.Version, time.Hour); !errors.Is(err, domain.ErrConflict) { + if _, err := s.RenewLease("t", "worker-a", before.Lease.Epoch, before.Version, time.Hour); !errors.Is(err, domain.ErrConflict) { t.Fatalf("replayed renewal = %v, want conflict", err) } } +func TestLeaseEpochFencesStaleOwnerLifecycleWrites(t *testing.T) { + s, err := Open(t.TempDir()) + if err != nil { + t.Fatal(err) + } + if err := s.Append(domain.Event{ID: "create", Type: "TaskCreated", TaskID: "t", Version: 1, Payload: []byte(`{"source":"s","external_id":"epoch","project":"p"}`), Surface: string(authz.System)}); err != nil { + t.Fatal(err) + } + if _, err := s.Lease("t", "worker", time.Minute); err != nil { + t.Fatal(err) + } + first, _ := s.Task("t") + ref, err := s.PutArtifact([]byte("handoff")) + if err != nil { + t.Fatal(err) + } + staleRelease, _ := json.Marshal(map[string]any{"handoff_ref": ref, "anchor_sha": strings.Repeat("a", 40), "harness_id": "worker", "lease_epoch": "stale", "expected_version": first.Version}) + if err := s.Append(domain.Event{ID: "stale-release", Type: "TaskReleased", TaskID: "t", Version: first.Version + 1, Payload: staleRelease, Surface: string(authz.System)}); !errors.Is(err, domain.ErrConflict) { + t.Fatalf("stale release = %v, want conflict", err) + } + release, _ := json.Marshal(map[string]any{"handoff_ref": ref, "anchor_sha": strings.Repeat("a", 40), "harness_id": "worker", "lease_epoch": first.Lease.Epoch, "expected_version": first.Version}) + if err := s.Append(domain.Event{ID: "release", Type: "TaskReleased", TaskID: "t", Version: first.Version + 1, Payload: release, Surface: string(authz.System)}); err != nil { + t.Fatal(err) + } + if _, err := s.Lease("t", "worker", time.Minute); err != nil { + t.Fatal(err) + } + second, _ := s.Task("t") + if second.Lease.Epoch == first.Lease.Epoch { + t.Fatal("re-lease reused fencing epoch") + } + report, err := s.PutArtifact([]byte("report")) + if err != nil { + t.Fatal(err) + } + staleComplete, _ := json.Marshal(map[string]any{"report_ref": report, "receipt": map[string]any{"consumed": 1}, "harness_id": "worker", "lease_epoch": first.Lease.Epoch, "expected_version": second.Version}) + if err := s.Append(domain.Event{ID: "stale-complete", Type: "TaskCompleted", TaskID: "t", Version: second.Version + 1, Payload: staleComplete, Surface: string(authz.System)}); !errors.Is(err, domain.ErrConflict) { + t.Fatalf("stale completion = %v, want conflict", err) + } +} + +func TestOpenRebuildsOnlyFromLogAndIgnoresCorruptSnapshot(t *testing.T) { + dir := t.TempDir() + s, err := Open(dir) + if err != nil { + t.Fatal(err) + } + if err := s.Append(created("create")); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "snapshot.json"), []byte(`not json`), 0600); err != nil { + t.Fatal(err) + } + restarted, err := Open(dir) + if err != nil { + t.Fatalf("corrupt disposable snapshot prevented log recovery: %v", err) + } + if task, ok := restarted.Task("task-1"); !ok || task.State != domain.StateQueued { + t.Fatalf("log projection = %#v, present=%v", task, ok) + } +} + +func TestReplayLegacyLeaseDerivesNonRenewableFence(t *testing.T) { + dir := t.TempDir() + until := time.Now().Add(time.Hour).UnixNano() + events := []domain.Event{ + {SchemaVersion: 2, Seq: 1, ID: "create", Type: "TaskCreated", TaskID: "t", Version: 1, Payload: []byte(`{"source":"s","external_id":"legacy","project":"p"}`), Surface: string(authz.System)}, + {SchemaVersion: 2, Seq: 2, ID: "lease", Type: "TaskLeased", TaskID: "t", Version: 2, Payload: []byte(`{"harness_id":"worker","until_ns":` + fmt.Sprint(until) + `,"expected_version":1}`), Surface: string(authz.System)}, + } + f, err := os.Create(filepath.Join(dir, "events.jsonl")) + if err != nil { + t.Fatal(err) + } + for _, e := range events { + b, _ := json.Marshal(e) + if _, err := f.Write(append(b, '\n')); err != nil { + _ = f.Close() + t.Fatal(err) + } + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + s, err := Open(dir) + if err != nil { + t.Fatal(err) + } + task, _ := s.Task("t") + if task.Lease == nil || task.Lease.Epoch != "legacy:lease" { + t.Fatalf("legacy lease fence = %#v", task.Lease) + } + if _, err := s.RenewLease("t", "worker", "", task.Version, time.Hour); !errors.Is(err, domain.ErrConflict) { + t.Fatalf("legacy lease renewed without derived fence: %v", err) + } +} + func TestBlockedTaskProjectsStructuredDiagnosisAndLegacyFallback(t *testing.T) { s, err := Open(t.TempDir()) if err != nil { @@ -407,7 +504,8 @@ func TestReleaseTransactionSurvivesReLeaseUntilMatchingPickup(t *testing.T) { t.Fatal(err) } anchor := strings.Repeat("a", 40) - p, _ := json.Marshal(map[string]any{"handoff_ref": ref, "anchor_sha": anchor, "transaction_id": "tx-1", "expected_version": leased.Version}) + first, _ := s.Task("task-1") + p, _ := json.Marshal(map[string]any{"handoff_ref": ref, "anchor_sha": anchor, "transaction_id": "tx-1", "harness_id": first.Lease.HarnessID, "lease_epoch": first.Lease.Epoch, "expected_version": leased.Version}) if err := s.Append(domain.Event{ID: "release", Type: "TaskReleased", TaskID: "task-1", Version: leased.Version + 1, Payload: p, Surface: string(authz.System)}); err != nil { t.Fatal(err) } @@ -418,7 +516,7 @@ func TestReleaseTransactionSurvivesReLeaseUntilMatchingPickup(t *testing.T) { if task.ReleaseTransaction != "tx-1" || task.ReleaseAnchor != anchor || task.HandoffRef != ref { t.Fatalf("re-lease lost transaction: %+v", task) } - p, _ = json.Marshal(map[string]any{"transaction_id": "tx-1", "handoff_ref": ref, "anchor_sha": anchor, "harness_id": "successor", "lease_version": task.Version, "expected_version": task.Version}) + p, _ = json.Marshal(map[string]any{"transaction_id": "tx-1", "handoff_ref": ref, "anchor_sha": anchor, "harness_id": "successor", "lease_epoch": task.Lease.Epoch, "lease_version": task.Version, "expected_version": task.Version}) if err := s.Append(domain.Event{ID: "pickup", Type: "TaskPickupValidated", TaskID: "task-1", Version: task.Version + 1, Payload: p, Surface: string(authz.System)}); err != nil { t.Fatal(err) } diff --git a/internal/ui/ui.go b/internal/ui/ui.go index 935a7ce..d5eb55f 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -422,6 +422,11 @@ func (s Server) action(w http.ResponseWriter, r *http.Request, id, action string http.Error(w, "unknown action", 404) return } + if action == "block" { + // A browser-created block is an explicit operator decision. Preserve + // that fact even if its prose happens to contain a system keyword. + body["block_reason"] = string(domain.BlockReasonOperator) + } b, _ := json.Marshal(body) e := domain.Event{ID: domain.NewID(), Type: typ, TaskID: id, Version: t.Version + 1, Payload: b, Surface: string(authz.Web)} if err := s.Store.Append(e); err != nil { diff --git a/internal/webui/assets/assets/index-D1Up3m4b.js b/internal/webui/assets/assets/index-BXQHTW_a.js similarity index 61% rename from internal/webui/assets/assets/index-D1Up3m4b.js rename to internal/webui/assets/assets/index-BXQHTW_a.js index b97ca49..11a1817 100644 --- a/internal/webui/assets/assets/index-D1Up3m4b.js +++ b/internal/webui/assets/assets/index-BXQHTW_a.js @@ -1,4 +1,4 @@ -var Pm=i=>{throw TypeError(i)};var uf=(i,u,r)=>u.has(i)||Pm("Cannot "+r);var S=(i,u,r)=>(uf(i,u,"read from private field"),r?r.call(i):u.get(i)),P=(i,u,r)=>u.has(i)?Pm("Cannot add the same private member more than once"):u instanceof WeakSet?u.add(i):u.set(i,r),k=(i,u,r,c)=>(uf(i,u,"write to private field"),c?c.call(i,r):u.set(i,r),r),re=(i,u,r)=>(uf(i,u,"access private method"),r);var Ts=(i,u,r,c)=>({set _(o){k(i,u,o,r)},get _(){return S(i,u,c)}});(function(){const u=document.createElement("link").relList;if(u&&u.supports&&u.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))c(o);new MutationObserver(o=>{for(const m of o)if(m.type==="childList")for(const y of m.addedNodes)y.tagName==="LINK"&&y.rel==="modulepreload"&&c(y)}).observe(document,{childList:!0,subtree:!0});function r(o){const m={};return o.integrity&&(m.integrity=o.integrity),o.referrerPolicy&&(m.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?m.credentials="include":o.crossOrigin==="anonymous"?m.credentials="omit":m.credentials="same-origin",m}function c(o){if(o.ep)return;o.ep=!0;const m=r(o);fetch(o.href,m)}})();function Zg(i){return i&&i.__esModule&&Object.prototype.hasOwnProperty.call(i,"default")?i.default:i}var sf={exports:{}},Gi={};/** +var Pm=i=>{throw TypeError(i)};var uf=(i,u,r)=>u.has(i)||Pm("Cannot "+r);var S=(i,u,r)=>(uf(i,u,"read from private field"),r?r.call(i):u.get(i)),P=(i,u,r)=>u.has(i)?Pm("Cannot add the same private member more than once"):u instanceof WeakSet?u.add(i):u.set(i,r),V=(i,u,r,c)=>(uf(i,u,"write to private field"),c?c.call(i,r):u.set(i,r),r),re=(i,u,r)=>(uf(i,u,"access private method"),r);var js=(i,u,r,c)=>({set _(o){V(i,u,o,r)},get _(){return S(i,u,c)}});(function(){const u=document.createElement("link").relList;if(u&&u.supports&&u.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))c(o);new MutationObserver(o=>{for(const m of o)if(m.type==="childList")for(const y of m.addedNodes)y.tagName==="LINK"&&y.rel==="modulepreload"&&c(y)}).observe(document,{childList:!0,subtree:!0});function r(o){const m={};return o.integrity&&(m.integrity=o.integrity),o.referrerPolicy&&(m.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?m.credentials="include":o.crossOrigin==="anonymous"?m.credentials="omit":m.credentials="same-origin",m}function c(o){if(o.ep)return;o.ep=!0;const m=r(o);fetch(o.href,m)}})();function kg(i){return i&&i.__esModule&&Object.prototype.hasOwnProperty.call(i,"default")?i.default:i}var sf={exports:{}},Gi={};/** * @license React * react-jsx-runtime.production.js * @@ -6,7 +6,7 @@ var Pm=i=>{throw TypeError(i)};var uf=(i,u,r)=>u.has(i)||Pm("Cannot "+r);var S=( * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var Im;function Kg(){if(Im)return Gi;Im=1;var i=Symbol.for("react.transitional.element"),u=Symbol.for("react.fragment");function r(c,o,m){var y=null;if(m!==void 0&&(y=""+m),o.key!==void 0&&(y=""+o.key),"key"in o){m={};for(var g in o)g!=="key"&&(m[g]=o[g])}else m=o;return o=m.ref,{$$typeof:i,type:c,key:y,ref:o!==void 0?o:null,props:m}}return Gi.Fragment=u,Gi.jsx=r,Gi.jsxs=r,Gi}var ey;function Vg(){return ey||(ey=1,sf.exports=Kg()),sf.exports}var h=Vg(),cf={exports:{}},ne={};/** + */var Im;function Zg(){if(Im)return Gi;Im=1;var i=Symbol.for("react.transitional.element"),u=Symbol.for("react.fragment");function r(c,o,m){var y=null;if(m!==void 0&&(y=""+m),o.key!==void 0&&(y=""+o.key),"key"in o){m={};for(var g in o)g!=="key"&&(m[g]=o[g])}else m=o;return o=m.ref,{$$typeof:i,type:c,key:y,ref:o!==void 0?o:null,props:m}}return Gi.Fragment=u,Gi.jsx=r,Gi.jsxs=r,Gi}var ey;function Kg(){return ey||(ey=1,sf.exports=Zg()),sf.exports}var h=Kg(),cf={exports:{}},ne={};/** * @license React * react.production.js * @@ -14,7 +14,7 @@ var Pm=i=>{throw TypeError(i)};var uf=(i,u,r)=>u.has(i)||Pm("Cannot "+r);var S=( * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var ty;function kg(){if(ty)return ne;ty=1;var i=Symbol.for("react.transitional.element"),u=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),c=Symbol.for("react.strict_mode"),o=Symbol.for("react.profiler"),m=Symbol.for("react.consumer"),y=Symbol.for("react.context"),g=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),v=Symbol.for("react.memo"),x=Symbol.for("react.lazy"),E=Symbol.for("react.activity"),z=Symbol.iterator;function N(j){return j===null||typeof j!="object"?null:(j=z&&j[z]||j["@@iterator"],typeof j=="function"?j:null)}var G={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Q=Object.assign,Y={};function B(j,L,J){this.props=j,this.context=L,this.refs=Y,this.updater=J||G}B.prototype.isReactComponent={},B.prototype.setState=function(j,L){if(typeof j!="object"&&typeof j!="function"&&j!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,j,L,"setState")},B.prototype.forceUpdate=function(j){this.updater.enqueueForceUpdate(this,j,"forceUpdate")};function X(){}X.prototype=B.prototype;function U(j,L,J){this.props=j,this.context=L,this.refs=Y,this.updater=J||G}var Z=U.prototype=new X;Z.constructor=U,Q(Z,B.prototype),Z.isPureReactComponent=!0;var K=Array.isArray;function ie(){}var ee={H:null,A:null,T:null,S:null},ae=Object.prototype.hasOwnProperty;function ye(j,L,J){var $=J.ref;return{$$typeof:i,type:j,key:L,ref:$!==void 0?$:null,props:J}}function Me(j,L){return ye(j.type,L,j.props)}function Qe(j){return typeof j=="object"&&j!==null&&j.$$typeof===i}function Ye(j){var L={"=":"=0",":":"=2"};return"$"+j.replace(/[=:]/g,function(J){return L[J]})}var Et=/\/+/g;function et(j,L){return typeof j=="object"&&j!==null&&j.key!=null?Ye(""+j.key):L.toString(36)}function Ne(j){switch(j.status){case"fulfilled":return j.value;case"rejected":throw j.reason;default:switch(typeof j.status=="string"?j.then(ie,ie):(j.status="pending",j.then(function(L){j.status==="pending"&&(j.status="fulfilled",j.value=L)},function(L){j.status==="pending"&&(j.status="rejected",j.reason=L)})),j.status){case"fulfilled":return j.value;case"rejected":throw j.reason}}throw j}function w(j,L,J,$,ue){var fe=typeof j;(fe==="undefined"||fe==="boolean")&&(j=null);var Ee=!1;if(j===null)Ee=!0;else switch(fe){case"bigint":case"string":case"number":Ee=!0;break;case"object":switch(j.$$typeof){case i:case u:Ee=!0;break;case x:return Ee=j._init,w(Ee(j._payload),L,J,$,ue)}}if(Ee)return ue=ue(j),Ee=$===""?"."+et(j,0):$,K(ue)?(J="",Ee!=null&&(J=Ee.replace(Et,"$&/")+"/"),w(ue,L,J,"",function(Fn){return Fn})):ue!=null&&(Qe(ue)&&(ue=Me(ue,J+(ue.key==null||j&&j.key===ue.key?"":(""+ue.key).replace(Et,"$&/")+"/")+Ee)),L.push(ue)),1;Ee=0;var ct=$===""?".":$+":";if(K(j))for(var Ge=0;Ge{throw TypeError(i)};var uf=(i,u,r)=>u.has(i)||Pm("Cannot "+r);var S=( * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var ly;function Fg(){return ly||(ly=1,(function(i){function u(w,V){var le=w.length;w.push(V);e:for(;0>>1,_e=w[Re];if(0>>1;Reo(J,le))$<_e&&0>o(ue,J)?(w[Re]=ue,w[$]=le,Re=$):(w[Re]=J,w[L]=le,Re=L);else if($<_e&&0>o(ue,le))w[Re]=ue,w[$]=le,Re=$;else break e}}return V}function o(w,V){var le=w.sortIndex-V.sortIndex;return le!==0?le:w.id-V.id}if(i.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var m=performance;i.unstable_now=function(){return m.now()}}else{var y=Date,g=y.now();i.unstable_now=function(){return y.now()-g}}var p=[],v=[],x=1,E=null,z=3,N=!1,G=!1,Q=!1,Y=!1,B=typeof setTimeout=="function"?setTimeout:null,X=typeof clearTimeout=="function"?clearTimeout:null,U=typeof setImmediate<"u"?setImmediate:null;function Z(w){for(var V=r(v);V!==null;){if(V.callback===null)c(v);else if(V.startTime<=w)c(v),V.sortIndex=V.expirationTime,u(p,V);else break;V=r(v)}}function K(w){if(Q=!1,Z(w),!G)if(r(p)!==null)G=!0,ie||(ie=!0,Ye());else{var V=r(v);V!==null&&Ne(K,V.startTime-w)}}var ie=!1,ee=-1,ae=5,ye=-1;function Me(){return Y?!0:!(i.unstable_now()-yew&&Me());){var Re=E.callback;if(typeof Re=="function"){E.callback=null,z=E.priorityLevel;var _e=Re(E.expirationTime<=w);if(w=i.unstable_now(),typeof _e=="function"){E.callback=_e,Z(w),V=!0;break t}E===r(p)&&c(p),Z(w)}else c(p);E=r(p)}if(E!==null)V=!0;else{var j=r(v);j!==null&&Ne(K,j.startTime-w),V=!1}}break e}finally{E=null,z=le,N=!1}V=void 0}}finally{V?Ye():ie=!1}}}var Ye;if(typeof U=="function")Ye=function(){U(Qe)};else if(typeof MessageChannel<"u"){var Et=new MessageChannel,et=Et.port2;Et.port1.onmessage=Qe,Ye=function(){et.postMessage(null)}}else Ye=function(){B(Qe,0)};function Ne(w,V){ee=B(function(){w(i.unstable_now())},V)}i.unstable_IdlePriority=5,i.unstable_ImmediatePriority=1,i.unstable_LowPriority=4,i.unstable_NormalPriority=3,i.unstable_Profiling=null,i.unstable_UserBlockingPriority=2,i.unstable_cancelCallback=function(w){w.callback=null},i.unstable_forceFrameRate=function(w){0>w||125Re?(w.sortIndex=le,u(v,w),r(p)===null&&w===r(v)&&(Q?(X(ee),ee=-1):Q=!0,Ne(K,le-Re))):(w.sortIndex=_e,u(p,w),G||N||(G=!0,ie||(ie=!0,Ye()))),w},i.unstable_shouldYield=Me,i.unstable_wrapCallback=function(w){var V=z;return function(){var le=z;z=V;try{return w.apply(this,arguments)}finally{z=le}}}})(of)),of}var ny;function $g(){return ny||(ny=1,ff.exports=Fg()),ff.exports}var hf={exports:{}},ut={};/** + */var ly;function Fg(){return ly||(ly=1,(function(i){function u(w,K){var le=w.length;w.push(K);e:for(;0>>1,_e=w[Ce];if(0>>1;Ceo(J,le))$<_e&&0>o(ue,J)?(w[Ce]=ue,w[$]=le,Ce=$):(w[Ce]=J,w[Y]=le,Ce=Y);else if($<_e&&0>o(ue,le))w[Ce]=ue,w[$]=le,Ce=$;else break e}}return K}function o(w,K){var le=w.sortIndex-K.sortIndex;return le!==0?le:w.id-K.id}if(i.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var m=performance;i.unstable_now=function(){return m.now()}}else{var y=Date,g=y.now();i.unstable_now=function(){return y.now()-g}}var p=[],v=[],x=1,E=null,z=3,M=!1,G=!1,Q=!1,H=!1,B=typeof setTimeout=="function"?setTimeout:null,X=typeof clearTimeout=="function"?clearTimeout:null,U=typeof setImmediate<"u"?setImmediate:null;function k(w){for(var K=r(v);K!==null;){if(K.callback===null)c(v);else if(K.startTime<=w)c(v),K.sortIndex=K.expirationTime,u(p,K);else break;K=r(v)}}function Z(w){if(Q=!1,k(w),!G)if(r(p)!==null)G=!0,ie||(ie=!0,Ye());else{var K=r(v);K!==null&&Me(Z,K.startTime-w)}}var ie=!1,ee=-1,ae=5,ye=-1;function Ne(){return H?!0:!(i.unstable_now()-yew&&Ne());){var Ce=E.callback;if(typeof Ce=="function"){E.callback=null,z=E.priorityLevel;var _e=Ce(E.expirationTime<=w);if(w=i.unstable_now(),typeof _e=="function"){E.callback=_e,k(w),K=!0;break t}E===r(p)&&c(p),k(w)}else c(p);E=r(p)}if(E!==null)K=!0;else{var T=r(v);T!==null&&Me(Z,T.startTime-w),K=!1}}break e}finally{E=null,z=le,M=!1}K=void 0}}finally{K?Ye():ie=!1}}}var Ye;if(typeof U=="function")Ye=function(){U(Qe)};else if(typeof MessageChannel<"u"){var Et=new MessageChannel,et=Et.port2;Et.port1.onmessage=Qe,Ye=function(){et.postMessage(null)}}else Ye=function(){B(Qe,0)};function Me(w,K){ee=B(function(){w(i.unstable_now())},K)}i.unstable_IdlePriority=5,i.unstable_ImmediatePriority=1,i.unstable_LowPriority=4,i.unstable_NormalPriority=3,i.unstable_Profiling=null,i.unstable_UserBlockingPriority=2,i.unstable_cancelCallback=function(w){w.callback=null},i.unstable_forceFrameRate=function(w){0>w||125Ce?(w.sortIndex=le,u(v,w),r(p)===null&&w===r(v)&&(Q?(X(ee),ee=-1):Q=!0,Me(Z,le-Ce))):(w.sortIndex=_e,u(p,w),G||M||(G=!0,ie||(ie=!0,Ye()))),w},i.unstable_shouldYield=Ne,i.unstable_wrapCallback=function(w){var K=z;return function(){var le=z;z=K;try{return w.apply(this,arguments)}finally{z=le}}}})(of)),of}var ny;function $g(){return ny||(ny=1,ff.exports=Fg()),ff.exports}var hf={exports:{}},ut={};/** * @license React * react-dom.production.js * @@ -30,7 +30,7 @@ var Pm=i=>{throw TypeError(i)};var uf=(i,u,r)=>u.has(i)||Pm("Cannot "+r);var S=( * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var iy;function Wg(){if(iy)return ut;iy=1;var i=Hf();function u(p){var v="https://react.dev/errors/"+p;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(u){console.error(u)}}return i(),hf.exports=Wg(),hf.exports}/** + */var iy;function Wg(){if(iy)return ut;iy=1;var i=Hf();function u(p){var v="https://react.dev/errors/"+p;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(u){console.error(u)}}return i(),hf.exports=Wg(),hf.exports}/** * @license React * react-dom-client.production.js * @@ -38,15 +38,15 @@ var Pm=i=>{throw TypeError(i)};var uf=(i,u,r)=>u.has(i)||Pm("Cannot "+r);var S=( * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var sy;function Ig(){if(sy)return Xi;sy=1;var i=$g(),u=Hf(),r=Pg();function c(e){var t="https://react.dev/errors/"+e;if(1_e||(e.current=Re[_e],Re[_e]=null,_e--)}function J(e,t){_e++,Re[_e]=e.current,e.current=t}var $=j(null),ue=j(null),fe=j(null),Ee=j(null);function ct(e,t){switch(J(fe,t),J(ue,e),J($,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Em(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Em(t),e=xm(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}L($),J($,e)}function Ge(){L($),L(ue),L(fe)}function Fn(e){e.memoizedState!==null&&J(Ee,e);var t=$.current,a=xm(t,e.type);t!==a&&(J(ue,e),J($,a))}function iu(e){ue.current===e&&(L($),L(ue)),Ee.current===e&&(L(Ee),Bi._currentValue=le)}var Ys,$f;function pl(e){if(Ys===void 0)try{throw Error()}catch(a){var t=a.stack.trim().match(/\n( *(at )?)/);Ys=t&&t[1]||"",$f=-1_e||(e.current=Ce[_e],Ce[_e]=null,_e--)}function J(e,t){_e++,Ce[_e]=e.current,e.current=t}var $=T(null),ue=T(null),fe=T(null),Ee=T(null);function ct(e,t){switch(J(fe,t),J(ue,e),J($,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Em(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Em(t),e=xm(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}Y($),J($,e)}function Ge(){Y($),Y(ue),Y(fe)}function Fn(e){e.memoizedState!==null&&J(Ee,e);var t=$.current,a=xm(t,e.type);t!==a&&(J(ue,e),J($,a))}function iu(e){ue.current===e&&(Y($),Y(ue)),Ee.current===e&&(Y(Ee),Li._currentValue=le)}var Ys,$f;function pl(e){if(Ys===void 0)try{throw Error()}catch(a){var t=a.stack.trim().match(/\n( *(at )?)/);Ys=t&&t[1]||"",$f=-1)":-1n||b[l]!==A[n]){var D=` `+b[l].replace(" at new "," at ");return e.displayName&&D.includes("")&&(D=D.replace("",e.displayName)),D}while(1<=l&&0<=n);break}}}finally{Gs=!1,Error.prepareStackTrace=a}return(a=e?e.displayName||e.name:"")?pl(a):""}function Ev(e,t){switch(e.tag){case 26:case 27:case 5:return pl(e.type);case 16:return pl("Lazy");case 13:return e.child!==t&&t!==null?pl("Suspense Fallback"):pl("Suspense");case 19:return pl("SuspenseList");case 0:case 15:return Xs(e.type,!1);case 11:return Xs(e.type.render,!1);case 1:return Xs(e.type,!0);case 31:return pl("Activity");default:return""}}function Wf(e){try{var t="",a=null;do t+=Ev(e,a),a=e,e=e.return;while(e);return t}catch(l){return` Error generating stack: `+l.message+` -`+l.stack}}var Zs=Object.prototype.hasOwnProperty,Ks=i.unstable_scheduleCallback,Vs=i.unstable_cancelCallback,xv=i.unstable_shouldYield,Tv=i.unstable_requestPaint,xt=i.unstable_now,jv=i.unstable_getCurrentPriorityLevel,Pf=i.unstable_ImmediatePriority,If=i.unstable_UserBlockingPriority,uu=i.unstable_NormalPriority,Rv=i.unstable_LowPriority,eo=i.unstable_IdlePriority,Cv=i.log,Ov=i.unstable_setDisableYieldValue,$n=null,Tt=null;function wa(e){if(typeof Cv=="function"&&Ov(e),Tt&&typeof Tt.setStrictMode=="function")try{Tt.setStrictMode($n,e)}catch{}}var jt=Math.clz32?Math.clz32:zv,Av=Math.log,_v=Math.LN2;function zv(e){return e>>>=0,e===0?32:31-(Av(e)/_v|0)|0}var su=256,cu=262144,ru=4194304;function gl(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function fu(e,t,a){var l=e.pendingLanes;if(l===0)return 0;var n=0,s=e.suspendedLanes,f=e.pingedLanes;e=e.warmLanes;var d=l&134217727;return d!==0?(l=d&~s,l!==0?n=gl(l):(f&=d,f!==0?n=gl(f):a||(a=d&~e,a!==0&&(n=gl(a))))):(d=l&~s,d!==0?n=gl(d):f!==0?n=gl(f):a||(a=l&~e,a!==0&&(n=gl(a)))),n===0?0:t!==0&&t!==n&&(t&s)===0&&(s=n&-n,a=t&-t,s>=a||s===32&&(a&4194048)!==0)?t:n}function Wn(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function Mv(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function to(){var e=ru;return ru<<=1,(ru&62914560)===0&&(ru=4194304),e}function ks(e){for(var t=[],a=0;31>a;a++)t.push(e);return t}function Pn(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Nv(e,t,a,l,n,s){var f=e.pendingLanes;e.pendingLanes=a,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=a,e.entangledLanes&=a,e.errorRecoveryDisabledLanes&=a,e.shellSuspendCounter=0;var d=e.entanglements,b=e.expirationTimes,A=e.hiddenUpdates;for(a=f&~a;0"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var Bv=/[\n"\\]/g;function wt(e){return e.replace(Bv,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function Is(e,t,a,l,n,s,f,d){e.name="",f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?e.type=f:e.removeAttribute("type"),t!=null?f==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+Ut(t)):e.value!==""+Ut(t)&&(e.value=""+Ut(t)):f!=="submit"&&f!=="reset"||e.removeAttribute("value"),t!=null?ec(e,f,Ut(t)):a!=null?ec(e,f,Ut(a)):l!=null&&e.removeAttribute("value"),n==null&&s!=null&&(e.defaultChecked=!!s),n!=null&&(e.checked=n&&typeof n!="function"&&typeof n!="symbol"),d!=null&&typeof d!="function"&&typeof d!="symbol"&&typeof d!="boolean"?e.name=""+Ut(d):e.removeAttribute("name")}function yo(e,t,a,l,n,s,f,d){if(s!=null&&typeof s!="function"&&typeof s!="symbol"&&typeof s!="boolean"&&(e.type=s),t!=null||a!=null){if(!(s!=="submit"&&s!=="reset"||t!=null)){Ps(e);return}a=a!=null?""+Ut(a):"",t=t!=null?""+Ut(t):a,d||t===e.value||(e.value=t),e.defaultValue=t}l=l??n,l=typeof l!="function"&&typeof l!="symbol"&&!!l,e.checked=d?e.checked:!!l,e.defaultChecked=!!l,f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"&&(e.name=f),Ps(e)}function ec(e,t,a){t==="number"&&du(e.ownerDocument)===e||e.defaultValue===""+a||(e.defaultValue=""+a)}function Pl(e,t,a,l){if(e=e.options,t){t={};for(var n=0;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),ic=!1;if(oa)try{var ai={};Object.defineProperty(ai,"passive",{get:function(){ic=!0}}),window.addEventListener("test",ai,ai),window.removeEventListener("test",ai,ai)}catch{ic=!1}var qa=null,uc=null,yu=null;function xo(){if(yu)return yu;var e,t=uc,a=t.length,l,n="value"in qa?qa.value:qa.textContent,s=n.length;for(e=0;e=ii),Ao=" ",_o=!1;function zo(e,t){switch(e){case"keyup":return hp.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Mo(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var an=!1;function mp(e,t){switch(e){case"compositionend":return Mo(t);case"keypress":return t.which!==32?null:(_o=!0,Ao);case"textInput":return e=t.data,e===Ao&&_o?null:e;default:return null}}function yp(e,t){if(an)return e==="compositionend"||!oc&&zo(e,t)?(e=xo(),yu=uc=qa=null,an=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:a,offset:t-e};e=l}e:{for(;a;){if(a.nextSibling){a=a.nextSibling;break e}a=a.parentNode}a=void 0}a=Lo(a)}}function Yo(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Yo(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Go(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=du(e.document);t instanceof e.HTMLIFrameElement;){try{var a=typeof t.contentWindow.location.href=="string"}catch{a=!1}if(a)e=t.contentWindow;else break;t=du(e.document)}return t}function mc(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var Tp=oa&&"documentMode"in document&&11>=document.documentMode,ln=null,yc=null,ri=null,vc=!1;function Xo(e,t,a){var l=a.window===a?a.document:a.nodeType===9?a:a.ownerDocument;vc||ln==null||ln!==du(l)||(l=ln,"selectionStart"in l&&mc(l)?l={start:l.selectionStart,end:l.selectionEnd}:(l=(l.ownerDocument&&l.ownerDocument.defaultView||window).getSelection(),l={anchorNode:l.anchorNode,anchorOffset:l.anchorOffset,focusNode:l.focusNode,focusOffset:l.focusOffset}),ri&&ci(ri,l)||(ri=l,l=cs(yc,"onSelect"),0>=f,n-=f,It=1<<32-jt(t)+n|a<ce?(me=W,W=null):me=W.sibling;var be=_(R,W,O[ce],H);if(be===null){W===null&&(W=me);break}e&&W&&be.alternate===null&&t(R,W),T=s(be,T,ce),ge===null?I=be:ge.sibling=be,ge=be,W=me}if(ce===O.length)return a(R,W),ve&&da(R,ce),I;if(W===null){for(;cece?(me=W,W=null):me=W.sibling;var il=_(R,W,be.value,H);if(il===null){W===null&&(W=me);break}e&&W&&il.alternate===null&&t(R,W),T=s(il,T,ce),ge===null?I=il:ge.sibling=il,ge=il,W=me}if(be.done)return a(R,W),ve&&da(R,ce),I;if(W===null){for(;!be.done;ce++,be=O.next())be=q(R,be.value,H),be!==null&&(T=s(be,T,ce),ge===null?I=be:ge.sibling=be,ge=be);return ve&&da(R,ce),I}for(W=l(W);!be.done;ce++,be=O.next())be=M(W,R,ce,be.value,H),be!==null&&(e&&be.alternate!==null&&W.delete(be.key===null?ce:be.key),T=s(be,T,ce),ge===null?I=be:ge.sibling=be,ge=be);return e&&W.forEach(function(Xg){return t(R,Xg)}),ve&&da(R,ce),I}function Ae(R,T,O,H){if(typeof O=="object"&&O!==null&&O.type===Q&&O.key===null&&(O=O.props.children),typeof O=="object"&&O!==null){switch(O.$$typeof){case N:e:{for(var I=O.key;T!==null;){if(T.key===I){if(I=O.type,I===Q){if(T.tag===7){a(R,T.sibling),H=n(T,O.props.children),H.return=R,R=H;break e}}else if(T.elementType===I||typeof I=="object"&&I!==null&&I.$$typeof===ae&&_l(I)===T.type){a(R,T.sibling),H=n(T,O.props),yi(H,O),H.return=R,R=H;break e}a(R,T);break}else t(R,T);T=T.sibling}O.type===Q?(H=jl(O.props.children,R.mode,H,O.key),H.return=R,R=H):(H=Ru(O.type,O.key,O.props,null,R.mode,H),yi(H,O),H.return=R,R=H)}return f(R);case G:e:{for(I=O.key;T!==null;){if(T.key===I)if(T.tag===4&&T.stateNode.containerInfo===O.containerInfo&&T.stateNode.implementation===O.implementation){a(R,T.sibling),H=n(T,O.children||[]),H.return=R,R=H;break e}else{a(R,T);break}else t(R,T);T=T.sibling}H=Tc(O,R.mode,H),H.return=R,R=H}return f(R);case ae:return O=_l(O),Ae(R,T,O,H)}if(Ne(O))return F(R,T,O,H);if(Ye(O)){if(I=Ye(O),typeof I!="function")throw Error(c(150));return O=I.call(O),te(R,T,O,H)}if(typeof O.then=="function")return Ae(R,T,Nu(O),H);if(O.$$typeof===U)return Ae(R,T,Au(R,O),H);Du(R,O)}return typeof O=="string"&&O!==""||typeof O=="number"||typeof O=="bigint"?(O=""+O,T!==null&&T.tag===6?(a(R,T.sibling),H=n(T,O),H.return=R,R=H):(a(R,T),H=xc(O,R.mode,H),H.return=R,R=H),f(R)):a(R,T)}return function(R,T,O,H){try{mi=0;var I=Ae(R,T,O,H);return yn=null,I}catch(W){if(W===mn||W===zu)throw W;var ge=Ct(29,W,null,R.mode);return ge.lanes=H,ge.return=R,ge}finally{}}}var Ml=hh(!0),dh=hh(!1),Ga=!1;function wc(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Hc(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Xa(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Za(e,t,a){var l=e.updateQueue;if(l===null)return null;if(l=l.shared,(Se&2)!==0){var n=l.pending;return n===null?t.next=t:(t.next=n.next,n.next=t),l.pending=t,t=ju(e),$o(e,null,a),t}return Tu(e,l,t,a),ju(e)}function vi(e,t,a){if(t=t.updateQueue,t!==null&&(t=t.shared,(a&4194048)!==0)){var l=t.lanes;l&=e.pendingLanes,a|=l,t.lanes=a,lo(e,a)}}function qc(e,t){var a=e.updateQueue,l=e.alternate;if(l!==null&&(l=l.updateQueue,a===l)){var n=null,s=null;if(a=a.firstBaseUpdate,a!==null){do{var f={lane:a.lane,tag:a.tag,payload:a.payload,callback:null,next:null};s===null?n=s=f:s=s.next=f,a=a.next}while(a!==null);s===null?n=s=t:s=s.next=t}else n=s=t;a={baseState:l.baseState,firstBaseUpdate:n,lastBaseUpdate:s,shared:l.shared,callbacks:l.callbacks},e.updateQueue=a;return}e=a.lastBaseUpdate,e===null?a.firstBaseUpdate=t:e.next=t,a.lastBaseUpdate=t}var Bc=!1;function pi(){if(Bc){var e=dn;if(e!==null)throw e}}function gi(e,t,a,l){Bc=!1;var n=e.updateQueue;Ga=!1;var s=n.firstBaseUpdate,f=n.lastBaseUpdate,d=n.shared.pending;if(d!==null){n.shared.pending=null;var b=d,A=b.next;b.next=null,f===null?s=A:f.next=A,f=b;var D=e.alternate;D!==null&&(D=D.updateQueue,d=D.lastBaseUpdate,d!==f&&(d===null?D.firstBaseUpdate=A:d.next=A,D.lastBaseUpdate=b))}if(s!==null){var q=n.baseState;f=0,D=A=b=null,d=s;do{var _=d.lane&-536870913,M=_!==d.lane;if(M?(de&_)===_:(l&_)===_){_!==0&&_===hn&&(Bc=!0),D!==null&&(D=D.next={lane:0,tag:d.tag,payload:d.payload,callback:null,next:null});e:{var F=e,te=d;_=t;var Ae=a;switch(te.tag){case 1:if(F=te.payload,typeof F=="function"){q=F.call(Ae,q,_);break e}q=F;break e;case 3:F.flags=F.flags&-65537|128;case 0:if(F=te.payload,_=typeof F=="function"?F.call(Ae,q,_):F,_==null)break e;q=E({},q,_);break e;case 2:Ga=!0}}_=d.callback,_!==null&&(e.flags|=64,M&&(e.flags|=8192),M=n.callbacks,M===null?n.callbacks=[_]:M.push(_))}else M={lane:_,tag:d.tag,payload:d.payload,callback:d.callback,next:null},D===null?(A=D=M,b=q):D=D.next=M,f|=_;if(d=d.next,d===null){if(d=n.shared.pending,d===null)break;M=d,d=M.next,M.next=null,n.lastBaseUpdate=M,n.shared.pending=null}}while(!0);D===null&&(b=q),n.baseState=b,n.firstBaseUpdate=A,n.lastBaseUpdate=D,s===null&&(n.shared.lanes=0),Fa|=f,e.lanes=f,e.memoizedState=q}}function mh(e,t){if(typeof e!="function")throw Error(c(191,e));e.call(t)}function yh(e,t){var a=e.callbacks;if(a!==null)for(e.callbacks=null,e=0;es?s:8;var f=w.T,d={};w.T=d,lr(e,!1,t,a);try{var b=n(),A=w.S;if(A!==null&&A(d,b),b!==null&&typeof b=="object"&&typeof b.then=="function"){var D=Np(b,l);Ei(e,t,D,Mt(e))}else Ei(e,t,l,Mt(e))}catch(q){Ei(e,t,{then:function(){},status:"rejected",reason:q},Mt())}finally{V.p=s,f!==null&&d.types!==null&&(f.types=d.types),w.T=f}}function Bp(){}function tr(e,t,a,l){if(e.tag!==5)throw Error(c(476));var n=kh(e).queue;Vh(e,n,t,le,a===null?Bp:function(){return Jh(e),a(l)})}function kh(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:le,baseState:le,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:pa,lastRenderedState:le},next:null};var a={};return t.next={memoizedState:a,baseState:a,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:pa,lastRenderedState:a},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Jh(e){var t=kh(e);t.next===null&&(t=e.alternate.memoizedState),Ei(e,t.next.queue,{},Mt())}function ar(){return lt(Bi)}function Fh(){return Ze().memoizedState}function $h(){return Ze().memoizedState}function Lp(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var a=Mt();e=Xa(a);var l=Za(t,e,a);l!==null&&(gt(l,t,a),vi(l,t,a)),t={cache:Mc()},e.payload=t;return}t=t.return}}function Qp(e,t,a){var l=Mt();a={lane:l,revertLane:0,gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},Xu(e)?Ph(t,a):(a=Sc(e,t,a,l),a!==null&&(gt(a,e,l),Ih(a,t,l)))}function Wh(e,t,a){var l=Mt();Ei(e,t,a,l)}function Ei(e,t,a,l){var n={lane:l,revertLane:0,gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null};if(Xu(e))Ph(t,n);else{var s=e.alternate;if(e.lanes===0&&(s===null||s.lanes===0)&&(s=t.lastRenderedReducer,s!==null))try{var f=t.lastRenderedState,d=s(f,a);if(n.hasEagerState=!0,n.eagerState=d,Rt(d,f))return Tu(e,t,n,0),ze===null&&xu(),!1}catch{}finally{}if(a=Sc(e,t,n,l),a!==null)return gt(a,e,l),Ih(a,t,l),!0}return!1}function lr(e,t,a,l){if(l={lane:2,revertLane:wr(),gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},Xu(e)){if(t)throw Error(c(479))}else t=Sc(e,a,l,2),t!==null&>(t,e,2)}function Xu(e){var t=e.alternate;return e===se||t!==null&&t===se}function Ph(e,t){pn=Hu=!0;var a=e.pending;a===null?t.next=t:(t.next=a.next,a.next=t),e.pending=t}function Ih(e,t,a){if((a&4194048)!==0){var l=t.lanes;l&=e.pendingLanes,a|=l,t.lanes=a,lo(e,a)}}var xi={readContext:lt,use:Lu,useCallback:qe,useContext:qe,useEffect:qe,useImperativeHandle:qe,useLayoutEffect:qe,useInsertionEffect:qe,useMemo:qe,useReducer:qe,useRef:qe,useState:qe,useDebugValue:qe,useDeferredValue:qe,useTransition:qe,useSyncExternalStore:qe,useId:qe,useHostTransitionStatus:qe,useFormState:qe,useActionState:qe,useOptimistic:qe,useMemoCache:qe,useCacheRefresh:qe};xi.useEffectEvent=qe;var ed={readContext:lt,use:Lu,useCallback:function(e,t){return rt().memoizedState=[e,t===void 0?null:t],e},useContext:lt,useEffect:qh,useImperativeHandle:function(e,t,a){a=a!=null?a.concat([e]):null,Yu(4194308,4,Yh.bind(null,t,e),a)},useLayoutEffect:function(e,t){return Yu(4194308,4,e,t)},useInsertionEffect:function(e,t){Yu(4,2,e,t)},useMemo:function(e,t){var a=rt();t=t===void 0?null:t;var l=e();if(Nl){wa(!0);try{e()}finally{wa(!1)}}return a.memoizedState=[l,t],l},useReducer:function(e,t,a){var l=rt();if(a!==void 0){var n=a(t);if(Nl){wa(!0);try{a(t)}finally{wa(!1)}}}else n=t;return l.memoizedState=l.baseState=n,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:n},l.queue=e,e=e.dispatch=Qp.bind(null,se,e),[l.memoizedState,e]},useRef:function(e){var t=rt();return e={current:e},t.memoizedState=e},useState:function(e){e=$c(e);var t=e.queue,a=Wh.bind(null,se,t);return t.dispatch=a,[e.memoizedState,a]},useDebugValue:Ic,useDeferredValue:function(e,t){var a=rt();return er(a,e,t)},useTransition:function(){var e=$c(!1);return e=Vh.bind(null,se,e.queue,!0,!1),rt().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,a){var l=se,n=rt();if(ve){if(a===void 0)throw Error(c(407));a=a()}else{if(a=t(),ze===null)throw Error(c(349));(de&127)!==0||Eh(l,t,a)}n.memoizedState=a;var s={value:a,getSnapshot:t};return n.queue=s,qh(Th.bind(null,l,s,e),[e]),l.flags|=2048,bn(9,{destroy:void 0},xh.bind(null,l,s,a,t),null),a},useId:function(){var e=rt(),t=ze.identifierPrefix;if(ve){var a=ea,l=It;a=(l&~(1<<32-jt(l)-1)).toString(32)+a,t="_"+t+"R_"+a,a=qu++,0<\/script>",s=s.removeChild(s.firstChild);break;case"select":s=typeof l.is=="string"?f.createElement("select",{is:l.is}):f.createElement("select"),l.multiple?s.multiple=!0:l.size&&(s.size=l.size);break;default:s=typeof l.is=="string"?f.createElement(n,{is:l.is}):f.createElement(n)}}s[tt]=t,s[ht]=l;e:for(f=t.child;f!==null;){if(f.tag===5||f.tag===6)s.appendChild(f.stateNode);else if(f.tag!==4&&f.tag!==27&&f.child!==null){f.child.return=f,f=f.child;continue}if(f===t)break e;for(;f.sibling===null;){if(f.return===null||f.return===t)break e;f=f.return}f.sibling.return=f.return,f=f.sibling}t.stateNode=s;e:switch(it(s,n,l),n){case"button":case"input":case"select":case"textarea":l=!!l.autoFocus;break e;case"img":l=!0;break e;default:l=!1}l&&ba(t)}}return Ue(t),pr(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,a),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==l&&ba(t);else{if(typeof l!="string"&&t.stateNode===null)throw Error(c(166));if(e=fe.current,fn(t)){if(e=t.stateNode,a=t.memoizedProps,l=null,n=at,n!==null)switch(n.tag){case 27:case 5:l=n.memoizedProps}e[tt]=t,e=!!(e.nodeValue===a||l!==null&&l.suppressHydrationWarning===!0||bm(e.nodeValue,a)),e||Qa(t,!0)}else e=rs(e).createTextNode(l),e[tt]=t,t.stateNode=e}return Ue(t),null;case 31:if(a=t.memoizedState,e===null||e.memoizedState!==null){if(l=fn(t),a!==null){if(e===null){if(!l)throw Error(c(318));if(e=t.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(c(557));e[tt]=t}else Rl(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Ue(t),e=!1}else a=Oc(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),e=!0;if(!e)return t.flags&256?(At(t),t):(At(t),null);if((t.flags&128)!==0)throw Error(c(558))}return Ue(t),null;case 13:if(l=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(n=fn(t),l!==null&&l.dehydrated!==null){if(e===null){if(!n)throw Error(c(318));if(n=t.memoizedState,n=n!==null?n.dehydrated:null,!n)throw Error(c(317));n[tt]=t}else Rl(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Ue(t),n=!1}else n=Oc(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),n=!0;if(!n)return t.flags&256?(At(t),t):(At(t),null)}return At(t),(t.flags&128)!==0?(t.lanes=a,t):(a=l!==null,e=e!==null&&e.memoizedState!==null,a&&(l=t.child,n=null,l.alternate!==null&&l.alternate.memoizedState!==null&&l.alternate.memoizedState.cachePool!==null&&(n=l.alternate.memoizedState.cachePool.pool),s=null,l.memoizedState!==null&&l.memoizedState.cachePool!==null&&(s=l.memoizedState.cachePool.pool),s!==n&&(l.flags|=2048)),a!==e&&a&&(t.child.flags|=8192),Ju(t,t.updateQueue),Ue(t),null);case 4:return Ge(),e===null&&Lr(t.stateNode.containerInfo),Ue(t),null;case 10:return ya(t.type),Ue(t),null;case 19:if(L(Xe),l=t.memoizedState,l===null)return Ue(t),null;if(n=(t.flags&128)!==0,s=l.rendering,s===null)if(n)ji(l,!1);else{if(Be!==0||e!==null&&(e.flags&128)!==0)for(e=t.child;e!==null;){if(s=wu(e),s!==null){for(t.flags|=128,ji(l,!1),e=s.updateQueue,t.updateQueue=e,Ju(t,e),t.subtreeFlags=0,e=a,a=t.child;a!==null;)Wo(a,e),a=a.sibling;return J(Xe,Xe.current&1|2),ve&&da(t,l.treeForkCount),t.child}e=e.sibling}l.tail!==null&&xt()>Iu&&(t.flags|=128,n=!0,ji(l,!1),t.lanes=4194304)}else{if(!n)if(e=wu(s),e!==null){if(t.flags|=128,n=!0,e=e.updateQueue,t.updateQueue=e,Ju(t,e),ji(l,!0),l.tail===null&&l.tailMode==="hidden"&&!s.alternate&&!ve)return Ue(t),null}else 2*xt()-l.renderingStartTime>Iu&&a!==536870912&&(t.flags|=128,n=!0,ji(l,!1),t.lanes=4194304);l.isBackwards?(s.sibling=t.child,t.child=s):(e=l.last,e!==null?e.sibling=s:t.child=s,l.last=s)}return l.tail!==null?(e=l.tail,l.rendering=e,l.tail=e.sibling,l.renderingStartTime=xt(),e.sibling=null,a=Xe.current,J(Xe,n?a&1|2:a&1),ve&&da(t,l.treeForkCount),e):(Ue(t),null);case 22:case 23:return At(t),Qc(),l=t.memoizedState!==null,e!==null?e.memoizedState!==null!==l&&(t.flags|=8192):l&&(t.flags|=8192),l?(a&536870912)!==0&&(t.flags&128)===0&&(Ue(t),t.subtreeFlags&6&&(t.flags|=8192)):Ue(t),a=t.updateQueue,a!==null&&Ju(t,a.retryQueue),a=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),l=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(l=t.memoizedState.cachePool.pool),l!==a&&(t.flags|=2048),e!==null&&L(Al),null;case 24:return a=null,e!==null&&(a=e.memoizedState.cache),t.memoizedState.cache!==a&&(t.flags|=2048),ya(Ke),Ue(t),null;case 25:return null;case 30:return null}throw Error(c(156,t.tag))}function Kp(e,t){switch(Rc(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return ya(Ke),Ge(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return iu(t),null;case 31:if(t.memoizedState!==null){if(At(t),t.alternate===null)throw Error(c(340));Rl()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(At(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(c(340));Rl()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return L(Xe),null;case 4:return Ge(),null;case 10:return ya(t.type),null;case 22:case 23:return At(t),Qc(),e!==null&&L(Al),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return ya(Ke),null;case 25:return null;default:return null}}function jd(e,t){switch(Rc(t),t.tag){case 3:ya(Ke),Ge();break;case 26:case 27:case 5:iu(t);break;case 4:Ge();break;case 31:t.memoizedState!==null&&At(t);break;case 13:At(t);break;case 19:L(Xe);break;case 10:ya(t.type);break;case 22:case 23:At(t),Qc(),e!==null&&L(Al);break;case 24:ya(Ke)}}function Ri(e,t){try{var a=t.updateQueue,l=a!==null?a.lastEffect:null;if(l!==null){var n=l.next;a=n;do{if((a.tag&e)===e){l=void 0;var s=a.create,f=a.inst;l=s(),f.destroy=l}a=a.next}while(a!==n)}}catch(d){je(t,t.return,d)}}function ka(e,t,a){try{var l=t.updateQueue,n=l!==null?l.lastEffect:null;if(n!==null){var s=n.next;l=s;do{if((l.tag&e)===e){var f=l.inst,d=f.destroy;if(d!==void 0){f.destroy=void 0,n=t;var b=a,A=d;try{A()}catch(D){je(n,b,D)}}}l=l.next}while(l!==s)}}catch(D){je(t,t.return,D)}}function Rd(e){var t=e.updateQueue;if(t!==null){var a=e.stateNode;try{yh(t,a)}catch(l){je(e,e.return,l)}}}function Cd(e,t,a){a.props=Dl(e.type,e.memoizedProps),a.state=e.memoizedState;try{a.componentWillUnmount()}catch(l){je(e,t,l)}}function Ci(e,t){try{var a=e.ref;if(a!==null){switch(e.tag){case 26:case 27:case 5:var l=e.stateNode;break;case 30:l=e.stateNode;break;default:l=e.stateNode}typeof a=="function"?e.refCleanup=a(l):a.current=l}}catch(n){je(e,t,n)}}function ta(e,t){var a=e.ref,l=e.refCleanup;if(a!==null)if(typeof l=="function")try{l()}catch(n){je(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof a=="function")try{a(null)}catch(n){je(e,t,n)}else a.current=null}function Od(e){var t=e.type,a=e.memoizedProps,l=e.stateNode;try{e:switch(t){case"button":case"input":case"select":case"textarea":a.autoFocus&&l.focus();break e;case"img":a.src?l.src=a.src:a.srcSet&&(l.srcset=a.srcSet)}}catch(n){je(e,e.return,n)}}function gr(e,t,a){try{var l=e.stateNode;dg(l,e.type,a,t),l[ht]=t}catch(n){je(e,e.return,n)}}function Ad(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&el(e.type)||e.tag===4}function br(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Ad(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&el(e.type)||e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Sr(e,t,a){var l=e.tag;if(l===5||l===6)e=e.stateNode,t?(a.nodeType===9?a.body:a.nodeName==="HTML"?a.ownerDocument.body:a).insertBefore(e,t):(t=a.nodeType===9?a.body:a.nodeName==="HTML"?a.ownerDocument.body:a,t.appendChild(e),a=a._reactRootContainer,a!=null||t.onclick!==null||(t.onclick=fa));else if(l!==4&&(l===27&&el(e.type)&&(a=e.stateNode,t=null),e=e.child,e!==null))for(Sr(e,t,a),e=e.sibling;e!==null;)Sr(e,t,a),e=e.sibling}function Fu(e,t,a){var l=e.tag;if(l===5||l===6)e=e.stateNode,t?a.insertBefore(e,t):a.appendChild(e);else if(l!==4&&(l===27&&el(e.type)&&(a=e.stateNode),e=e.child,e!==null))for(Fu(e,t,a),e=e.sibling;e!==null;)Fu(e,t,a),e=e.sibling}function _d(e){var t=e.stateNode,a=e.memoizedProps;try{for(var l=e.type,n=t.attributes;n.length;)t.removeAttributeNode(n[0]);it(t,l,a),t[tt]=e,t[ht]=a}catch(s){je(e,e.return,s)}}var Sa=!1,Je=!1,Er=!1,zd=typeof WeakSet=="function"?WeakSet:Set,Pe=null;function Vp(e,t){if(e=e.containerInfo,Gr=vs,e=Go(e),mc(e)){if("selectionStart"in e)var a={start:e.selectionStart,end:e.selectionEnd};else e:{a=(a=e.ownerDocument)&&a.defaultView||window;var l=a.getSelection&&a.getSelection();if(l&&l.rangeCount!==0){a=l.anchorNode;var n=l.anchorOffset,s=l.focusNode;l=l.focusOffset;try{a.nodeType,s.nodeType}catch{a=null;break e}var f=0,d=-1,b=-1,A=0,D=0,q=e,_=null;t:for(;;){for(var M;q!==a||n!==0&&q.nodeType!==3||(d=f+n),q!==s||l!==0&&q.nodeType!==3||(b=f+l),q.nodeType===3&&(f+=q.nodeValue.length),(M=q.firstChild)!==null;)_=q,q=M;for(;;){if(q===e)break t;if(_===a&&++A===n&&(d=f),_===s&&++D===l&&(b=f),(M=q.nextSibling)!==null)break;q=_,_=q.parentNode}q=M}a=d===-1||b===-1?null:{start:d,end:b}}else a=null}a=a||{start:0,end:0}}else a=null;for(Xr={focusedElem:e,selectionRange:a},vs=!1,Pe=t;Pe!==null;)if(t=Pe,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,Pe=e;else for(;Pe!==null;){switch(t=Pe,s=t.alternate,e=t.flags,t.tag){case 0:if((e&4)!==0&&(e=t.updateQueue,e=e!==null?e.events:null,e!==null))for(a=0;a title"))),it(s,l,a),s[tt]=e,We(s),l=s;break e;case"link":var f=Hm("link","href",n).get(l+(a.href||""));if(f){for(var d=0;dAe&&(f=Ae,Ae=te,te=f);var R=Qo(d,te),T=Qo(d,Ae);if(R&&T&&(M.rangeCount!==1||M.anchorNode!==R.node||M.anchorOffset!==R.offset||M.focusNode!==T.node||M.focusOffset!==T.offset)){var O=q.createRange();O.setStart(R.node,R.offset),M.removeAllRanges(),te>Ae?(M.addRange(O),M.extend(T.node,T.offset)):(O.setEnd(T.node,T.offset),M.addRange(O))}}}}for(q=[],M=d;M=M.parentNode;)M.nodeType===1&&q.push({element:M,left:M.scrollLeft,top:M.scrollTop});for(typeof d.focus=="function"&&d.focus(),d=0;da?32:a,w.T=null,a=Ar,Ar=null;var s=Wa,f=Ra;if(Fe=0,jn=Wa=null,Ra=0,(Se&6)!==0)throw Error(c(331));var d=Se;if(Se|=4,Yd(s.current),Bd(s,s.current,f,a),Se=d,Ni(0,!1),Tt&&typeof Tt.onPostCommitFiberRoot=="function")try{Tt.onPostCommitFiberRoot($n,s)}catch{}return!0}finally{V.p=n,w.T=l,im(e,t)}}function sm(e,t,a){t=qt(a,t),t=sr(e.stateNode,t,2),e=Za(e,t,2),e!==null&&(Pn(e,2),aa(e))}function je(e,t,a){if(e.tag===3)sm(e,e,a);else for(;t!==null;){if(t.tag===3){sm(t,e,a);break}else if(t.tag===1){var l=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof l.componentDidCatch=="function"&&($a===null||!$a.has(l))){e=qt(a,e),a=cd(2),l=Za(t,a,2),l!==null&&(rd(a,l,t,e),Pn(l,2),aa(l));break}}t=t.return}}function Nr(e,t,a){var l=e.pingCache;if(l===null){l=e.pingCache=new Fp;var n=new Set;l.set(t,n)}else n=l.get(t),n===void 0&&(n=new Set,l.set(t,n));n.has(a)||(jr=!0,n.add(a),e=eg.bind(null,e,t,a),t.then(e,e))}function eg(e,t,a){var l=e.pingCache;l!==null&&l.delete(t),e.pingedLanes|=e.suspendedLanes&a,e.warmLanes&=~a,ze===e&&(de&a)===a&&(Be===4||Be===3&&(de&62914560)===de&&300>xt()-Pu?(Se&2)===0&&Rn(e,0):Rr|=a,Tn===de&&(Tn=0)),aa(e)}function cm(e,t){t===0&&(t=to()),e=Tl(e,t),e!==null&&(Pn(e,t),aa(e))}function tg(e){var t=e.memoizedState,a=0;t!==null&&(a=t.retryLane),cm(e,a)}function ag(e,t){var a=0;switch(e.tag){case 31:case 13:var l=e.stateNode,n=e.memoizedState;n!==null&&(a=n.retryLane);break;case 19:l=e.stateNode;break;case 22:l=e.stateNode._retryCache;break;default:throw Error(c(314))}l!==null&&l.delete(t),cm(e,a)}function lg(e,t){return Ks(e,t)}var is=null,On=null,Dr=!1,us=!1,Ur=!1,Ia=0;function aa(e){e!==On&&e.next===null&&(On===null?is=On=e:On=On.next=e),us=!0,Dr||(Dr=!0,ig())}function Ni(e,t){if(!Ur&&us){Ur=!0;do for(var a=!1,l=is;l!==null;){if(e!==0){var n=l.pendingLanes;if(n===0)var s=0;else{var f=l.suspendedLanes,d=l.pingedLanes;s=(1<<31-jt(42|e)+1)-1,s&=n&~(f&~d),s=s&201326741?s&201326741|1:s?s|2:0}s!==0&&(a=!0,hm(l,s))}else s=de,s=fu(l,l===ze?s:0,l.cancelPendingCommit!==null||l.timeoutHandle!==-1),(s&3)===0||Wn(l,s)||(a=!0,hm(l,s));l=l.next}while(a);Ur=!1}}function ng(){rm()}function rm(){us=Dr=!1;var e=0;Ia!==0&&yg()&&(e=Ia);for(var t=xt(),a=null,l=is;l!==null;){var n=l.next,s=fm(l,t);s===0?(l.next=null,a===null?is=n:a.next=n,n===null&&(On=a)):(a=l,(e!==0||(s&3)!==0)&&(us=!0)),l=n}Fe!==0&&Fe!==5||Ni(e),Ia!==0&&(Ia=0)}function fm(e,t){for(var a=e.suspendedLanes,l=e.pingedLanes,n=e.expirationTimes,s=e.pendingLanes&-62914561;0d)break;var D=b.transferSize,q=b.initiatorType;D&&Sm(q)&&(b=b.responseEnd,f+=D*(b"u"?null:document;function Nm(e,t,a){var l=An;if(l&&typeof t=="string"&&t){var n=wt(t);n='link[rel="'+e+'"][href="'+n+'"]',typeof a=="string"&&(n+='[crossorigin="'+a+'"]'),Mm.has(n)||(Mm.add(n),e={rel:e,crossOrigin:a,href:t},l.querySelector(n)===null&&(t=l.createElement("link"),it(t,"link",e),We(t),l.head.appendChild(t)))}}function jg(e){Ca.D(e),Nm("dns-prefetch",e,null)}function Rg(e,t){Ca.C(e,t),Nm("preconnect",e,t)}function Cg(e,t,a){Ca.L(e,t,a);var l=An;if(l&&e&&t){var n='link[rel="preload"][as="'+wt(t)+'"]';t==="image"&&a&&a.imageSrcSet?(n+='[imagesrcset="'+wt(a.imageSrcSet)+'"]',typeof a.imageSizes=="string"&&(n+='[imagesizes="'+wt(a.imageSizes)+'"]')):n+='[href="'+wt(e)+'"]';var s=n;switch(t){case"style":s=_n(e);break;case"script":s=zn(e)}Xt.has(s)||(e=E({rel:"preload",href:t==="image"&&a&&a.imageSrcSet?void 0:e,as:t},a),Xt.set(s,e),l.querySelector(n)!==null||t==="style"&&l.querySelector(Hi(s))||t==="script"&&l.querySelector(qi(s))||(t=l.createElement("link"),it(t,"link",e),We(t),l.head.appendChild(t)))}}function Og(e,t){Ca.m(e,t);var a=An;if(a&&e){var l=t&&typeof t.as=="string"?t.as:"script",n='link[rel="modulepreload"][as="'+wt(l)+'"][href="'+wt(e)+'"]',s=n;switch(l){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":s=zn(e)}if(!Xt.has(s)&&(e=E({rel:"modulepreload",href:e},t),Xt.set(s,e),a.querySelector(n)===null)){switch(l){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(a.querySelector(qi(s)))return}l=a.createElement("link"),it(l,"link",e),We(l),a.head.appendChild(l)}}}function Ag(e,t,a){Ca.S(e,t,a);var l=An;if(l&&e){var n=$l(l).hoistableStyles,s=_n(e);t=t||"default";var f=n.get(s);if(!f){var d={loading:0,preload:null};if(f=l.querySelector(Hi(s)))d.loading=5;else{e=E({rel:"stylesheet",href:e,"data-precedence":t},a),(a=Xt.get(s))&&$r(e,a);var b=f=l.createElement("link");We(b),it(b,"link",e),b._p=new Promise(function(A,D){b.onload=A,b.onerror=D}),b.addEventListener("load",function(){d.loading|=1}),b.addEventListener("error",function(){d.loading|=2}),d.loading|=4,os(f,t,l)}f={type:"stylesheet",instance:f,count:1,state:d},n.set(s,f)}}}function _g(e,t){Ca.X(e,t);var a=An;if(a&&e){var l=$l(a).hoistableScripts,n=zn(e),s=l.get(n);s||(s=a.querySelector(qi(n)),s||(e=E({src:e,async:!0},t),(t=Xt.get(n))&&Wr(e,t),s=a.createElement("script"),We(s),it(s,"link",e),a.head.appendChild(s)),s={type:"script",instance:s,count:1,state:null},l.set(n,s))}}function zg(e,t){Ca.M(e,t);var a=An;if(a&&e){var l=$l(a).hoistableScripts,n=zn(e),s=l.get(n);s||(s=a.querySelector(qi(n)),s||(e=E({src:e,async:!0,type:"module"},t),(t=Xt.get(n))&&Wr(e,t),s=a.createElement("script"),We(s),it(s,"link",e),a.head.appendChild(s)),s={type:"script",instance:s,count:1,state:null},l.set(n,s))}}function Dm(e,t,a,l){var n=(n=fe.current)?fs(n):null;if(!n)throw Error(c(446));switch(e){case"meta":case"title":return null;case"style":return typeof a.precedence=="string"&&typeof a.href=="string"?(t=_n(a.href),a=$l(n).hoistableStyles,l=a.get(t),l||(l={type:"style",instance:null,count:0,state:null},a.set(t,l)),l):{type:"void",instance:null,count:0,state:null};case"link":if(a.rel==="stylesheet"&&typeof a.href=="string"&&typeof a.precedence=="string"){e=_n(a.href);var s=$l(n).hoistableStyles,f=s.get(e);if(f||(n=n.ownerDocument||n,f={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},s.set(e,f),(s=n.querySelector(Hi(e)))&&!s._p&&(f.instance=s,f.state.loading=5),Xt.has(e)||(a={rel:"preload",as:"style",href:a.href,crossOrigin:a.crossOrigin,integrity:a.integrity,media:a.media,hrefLang:a.hrefLang,referrerPolicy:a.referrerPolicy},Xt.set(e,a),s||Mg(n,e,a,f.state))),t&&l===null)throw Error(c(528,""));return f}if(t&&l!==null)throw Error(c(529,""));return null;case"script":return t=a.async,a=a.src,typeof a=="string"&&t&&typeof t!="function"&&typeof t!="symbol"?(t=zn(a),a=$l(n).hoistableScripts,l=a.get(t),l||(l={type:"script",instance:null,count:0,state:null},a.set(t,l)),l):{type:"void",instance:null,count:0,state:null};default:throw Error(c(444,e))}}function _n(e){return'href="'+wt(e)+'"'}function Hi(e){return'link[rel="stylesheet"]['+e+"]"}function Um(e){return E({},e,{"data-precedence":e.precedence,precedence:null})}function Mg(e,t,a,l){e.querySelector('link[rel="preload"][as="style"]['+t+"]")?l.loading=1:(t=e.createElement("link"),l.preload=t,t.addEventListener("load",function(){return l.loading|=1}),t.addEventListener("error",function(){return l.loading|=2}),it(t,"link",a),We(t),e.head.appendChild(t))}function zn(e){return'[src="'+wt(e)+'"]'}function qi(e){return"script[async]"+e}function wm(e,t,a){if(t.count++,t.instance===null)switch(t.type){case"style":var l=e.querySelector('style[data-href~="'+wt(a.href)+'"]');if(l)return t.instance=l,We(l),l;var n=E({},a,{"data-href":a.href,"data-precedence":a.precedence,href:null,precedence:null});return l=(e.ownerDocument||e).createElement("style"),We(l),it(l,"style",n),os(l,a.precedence,e),t.instance=l;case"stylesheet":n=_n(a.href);var s=e.querySelector(Hi(n));if(s)return t.state.loading|=4,t.instance=s,We(s),s;l=Um(a),(n=Xt.get(n))&&$r(l,n),s=(e.ownerDocument||e).createElement("link"),We(s);var f=s;return f._p=new Promise(function(d,b){f.onload=d,f.onerror=b}),it(s,"link",l),t.state.loading|=4,os(s,a.precedence,e),t.instance=s;case"script":return s=zn(a.src),(n=e.querySelector(qi(s)))?(t.instance=n,We(n),n):(l=a,(n=Xt.get(s))&&(l=E({},a),Wr(l,n)),e=e.ownerDocument||e,n=e.createElement("script"),We(n),it(n,"link",l),e.head.appendChild(n),t.instance=n);case"void":return null;default:throw Error(c(443,t.type))}else t.type==="stylesheet"&&(t.state.loading&4)===0&&(l=t.instance,t.state.loading|=4,os(l,a.precedence,e));return t.instance}function os(e,t,a){for(var l=a.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),n=l.length?l[l.length-1]:null,s=n,f=0;f title"):null)}function Ng(e,t,a){if(a===1||t.itemProp!=null)return!1;switch(e){case"meta":case"title":return!0;case"style":if(typeof t.precedence!="string"||typeof t.href!="string"||t.href==="")break;return!0;case"link":if(typeof t.rel!="string"||typeof t.href!="string"||t.href===""||t.onLoad||t.onError)break;switch(t.rel){case"stylesheet":return e=t.disabled,typeof t.precedence=="string"&&e==null;default:return!0}case"script":if(t.async&&typeof t.async!="function"&&typeof t.async!="symbol"&&!t.onLoad&&!t.onError&&t.src&&typeof t.src=="string")return!0}return!1}function Bm(e){return!(e.type==="stylesheet"&&(e.state.loading&3)===0)}function Dg(e,t,a,l){if(a.type==="stylesheet"&&(typeof l.media!="string"||matchMedia(l.media).matches!==!1)&&(a.state.loading&4)===0){if(a.instance===null){var n=_n(l.href),s=t.querySelector(Hi(n));if(s){t=s._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(e.count++,e=ds.bind(e),t.then(e,e)),a.state.loading|=4,a.instance=s,We(s);return}s=t.ownerDocument||t,l=Um(l),(n=Xt.get(n))&&$r(l,n),s=s.createElement("link"),We(s);var f=s;f._p=new Promise(function(d,b){f.onload=d,f.onerror=b}),it(s,"link",l),a.instance=s}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(a,t),(t=a.state.preload)&&(a.state.loading&3)===0&&(e.count++,a=ds.bind(e),t.addEventListener("load",a),t.addEventListener("error",a))}}var Pr=0;function Ug(e,t){return e.stylesheets&&e.count===0&&ys(e,e.stylesheets),0Pr?50:800)+t);return e.unsuspend=a,function(){e.unsuspend=null,clearTimeout(l),clearTimeout(n)}}:null}function ds(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)ys(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var ms=null;function ys(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,ms=new Map,t.forEach(wg,e),ms=null,ds.call(e))}function wg(e,t){if(!(t.state.loading&4)){var a=ms.get(e);if(a)var l=a.get(null);else{a=new Map,ms.set(e,a);for(var n=e.querySelectorAll("link[data-precedence],style[data-precedence]"),s=0;s"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(u){console.error(u)}}return i(),rf.exports=Ig(),rf.exports}var t0=e0();/** +`+l.stack}}var ks=Object.prototype.hasOwnProperty,Zs=i.unstable_scheduleCallback,Ks=i.unstable_cancelCallback,xv=i.unstable_shouldYield,jv=i.unstable_requestPaint,xt=i.unstable_now,Tv=i.unstable_getCurrentPriorityLevel,Pf=i.unstable_ImmediatePriority,If=i.unstable_UserBlockingPriority,uu=i.unstable_NormalPriority,Cv=i.unstable_LowPriority,eo=i.unstable_IdlePriority,Rv=i.log,Ov=i.unstable_setDisableYieldValue,$n=null,jt=null;function wa(e){if(typeof Rv=="function"&&Ov(e),jt&&typeof jt.setStrictMode=="function")try{jt.setStrictMode($n,e)}catch{}}var Tt=Math.clz32?Math.clz32:zv,Av=Math.log,_v=Math.LN2;function zv(e){return e>>>=0,e===0?32:31-(Av(e)/_v|0)|0}var su=256,cu=262144,ru=4194304;function gl(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function fu(e,t,a){var l=e.pendingLanes;if(l===0)return 0;var n=0,s=e.suspendedLanes,f=e.pingedLanes;e=e.warmLanes;var d=l&134217727;return d!==0?(l=d&~s,l!==0?n=gl(l):(f&=d,f!==0?n=gl(f):a||(a=d&~e,a!==0&&(n=gl(a))))):(d=l&~s,d!==0?n=gl(d):f!==0?n=gl(f):a||(a=l&~e,a!==0&&(n=gl(a)))),n===0?0:t!==0&&t!==n&&(t&s)===0&&(s=n&-n,a=t&-t,s>=a||s===32&&(a&4194048)!==0)?t:n}function Wn(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function Nv(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function to(){var e=ru;return ru<<=1,(ru&62914560)===0&&(ru=4194304),e}function Vs(e){for(var t=[],a=0;31>a;a++)t.push(e);return t}function Pn(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Mv(e,t,a,l,n,s){var f=e.pendingLanes;e.pendingLanes=a,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=a,e.entangledLanes&=a,e.errorRecoveryDisabledLanes&=a,e.shellSuspendCounter=0;var d=e.entanglements,b=e.expirationTimes,A=e.hiddenUpdates;for(a=f&~a;0"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var Lv=/[\n"\\]/g;function wt(e){return e.replace(Lv,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function Is(e,t,a,l,n,s,f,d){e.name="",f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?e.type=f:e.removeAttribute("type"),t!=null?f==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+Ut(t)):e.value!==""+Ut(t)&&(e.value=""+Ut(t)):f!=="submit"&&f!=="reset"||e.removeAttribute("value"),t!=null?ec(e,f,Ut(t)):a!=null?ec(e,f,Ut(a)):l!=null&&e.removeAttribute("value"),n==null&&s!=null&&(e.defaultChecked=!!s),n!=null&&(e.checked=n&&typeof n!="function"&&typeof n!="symbol"),d!=null&&typeof d!="function"&&typeof d!="symbol"&&typeof d!="boolean"?e.name=""+Ut(d):e.removeAttribute("name")}function yo(e,t,a,l,n,s,f,d){if(s!=null&&typeof s!="function"&&typeof s!="symbol"&&typeof s!="boolean"&&(e.type=s),t!=null||a!=null){if(!(s!=="submit"&&s!=="reset"||t!=null)){Ps(e);return}a=a!=null?""+Ut(a):"",t=t!=null?""+Ut(t):a,d||t===e.value||(e.value=t),e.defaultValue=t}l=l??n,l=typeof l!="function"&&typeof l!="symbol"&&!!l,e.checked=d?e.checked:!!l,e.defaultChecked=!!l,f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"&&(e.name=f),Ps(e)}function ec(e,t,a){t==="number"&&du(e.ownerDocument)===e||e.defaultValue===""+a||(e.defaultValue=""+a)}function Pl(e,t,a,l){if(e=e.options,t){t={};for(var n=0;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),ic=!1;if(oa)try{var ai={};Object.defineProperty(ai,"passive",{get:function(){ic=!0}}),window.addEventListener("test",ai,ai),window.removeEventListener("test",ai,ai)}catch{ic=!1}var qa=null,uc=null,yu=null;function xo(){if(yu)return yu;var e,t=uc,a=t.length,l,n="value"in qa?qa.value:qa.textContent,s=n.length;for(e=0;e=ii),Ao=" ",_o=!1;function zo(e,t){switch(e){case"keyup":return hp.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function No(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var an=!1;function mp(e,t){switch(e){case"compositionend":return No(t);case"keypress":return t.which!==32?null:(_o=!0,Ao);case"textInput":return e=t.data,e===Ao&&_o?null:e;default:return null}}function yp(e,t){if(an)return e==="compositionend"||!oc&&zo(e,t)?(e=xo(),yu=uc=qa=null,an=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:a,offset:t-e};e=l}e:{for(;a;){if(a.nextSibling){a=a.nextSibling;break e}a=a.parentNode}a=void 0}a=Bo(a)}}function Yo(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Yo(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Go(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=du(e.document);t instanceof e.HTMLIFrameElement;){try{var a=typeof t.contentWindow.location.href=="string"}catch{a=!1}if(a)e=t.contentWindow;else break;t=du(e.document)}return t}function mc(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var jp=oa&&"documentMode"in document&&11>=document.documentMode,ln=null,yc=null,ri=null,vc=!1;function Xo(e,t,a){var l=a.window===a?a.document:a.nodeType===9?a:a.ownerDocument;vc||ln==null||ln!==du(l)||(l=ln,"selectionStart"in l&&mc(l)?l={start:l.selectionStart,end:l.selectionEnd}:(l=(l.ownerDocument&&l.ownerDocument.defaultView||window).getSelection(),l={anchorNode:l.anchorNode,anchorOffset:l.anchorOffset,focusNode:l.focusNode,focusOffset:l.focusOffset}),ri&&ci(ri,l)||(ri=l,l=cs(yc,"onSelect"),0>=f,n-=f,It=1<<32-Tt(t)+n|a<ce?(me=W,W=null):me=W.sibling;var be=_(C,W,O[ce],q);if(be===null){W===null&&(W=me);break}e&&W&&be.alternate===null&&t(C,W),j=s(be,j,ce),ge===null?I=be:ge.sibling=be,ge=be,W=me}if(ce===O.length)return a(C,W),ve&&da(C,ce),I;if(W===null){for(;cece?(me=W,W=null):me=W.sibling;var il=_(C,W,be.value,q);if(il===null){W===null&&(W=me);break}e&&W&&il.alternate===null&&t(C,W),j=s(il,j,ce),ge===null?I=il:ge.sibling=il,ge=il,W=me}if(be.done)return a(C,W),ve&&da(C,ce),I;if(W===null){for(;!be.done;ce++,be=O.next())be=L(C,be.value,q),be!==null&&(j=s(be,j,ce),ge===null?I=be:ge.sibling=be,ge=be);return ve&&da(C,ce),I}for(W=l(W);!be.done;ce++,be=O.next())be=N(W,C,ce,be.value,q),be!==null&&(e&&be.alternate!==null&&W.delete(be.key===null?ce:be.key),j=s(be,j,ce),ge===null?I=be:ge.sibling=be,ge=be);return e&&W.forEach(function(Xg){return t(C,Xg)}),ve&&da(C,ce),I}function Ae(C,j,O,q){if(typeof O=="object"&&O!==null&&O.type===Q&&O.key===null&&(O=O.props.children),typeof O=="object"&&O!==null){switch(O.$$typeof){case M:e:{for(var I=O.key;j!==null;){if(j.key===I){if(I=O.type,I===Q){if(j.tag===7){a(C,j.sibling),q=n(j,O.props.children),q.return=C,C=q;break e}}else if(j.elementType===I||typeof I=="object"&&I!==null&&I.$$typeof===ae&&_l(I)===j.type){a(C,j.sibling),q=n(j,O.props),yi(q,O),q.return=C,C=q;break e}a(C,j);break}else t(C,j);j=j.sibling}O.type===Q?(q=Tl(O.props.children,C.mode,q,O.key),q.return=C,C=q):(q=Cu(O.type,O.key,O.props,null,C.mode,q),yi(q,O),q.return=C,C=q)}return f(C);case G:e:{for(I=O.key;j!==null;){if(j.key===I)if(j.tag===4&&j.stateNode.containerInfo===O.containerInfo&&j.stateNode.implementation===O.implementation){a(C,j.sibling),q=n(j,O.children||[]),q.return=C,C=q;break e}else{a(C,j);break}else t(C,j);j=j.sibling}q=jc(O,C.mode,q),q.return=C,C=q}return f(C);case ae:return O=_l(O),Ae(C,j,O,q)}if(Me(O))return F(C,j,O,q);if(Ye(O)){if(I=Ye(O),typeof I!="function")throw Error(c(150));return O=I.call(O),te(C,j,O,q)}if(typeof O.then=="function")return Ae(C,j,Mu(O),q);if(O.$$typeof===U)return Ae(C,j,Au(C,O),q);Du(C,O)}return typeof O=="string"&&O!==""||typeof O=="number"||typeof O=="bigint"?(O=""+O,j!==null&&j.tag===6?(a(C,j.sibling),q=n(j,O),q.return=C,C=q):(a(C,j),q=xc(O,C.mode,q),q.return=C,C=q),f(C)):a(C,j)}return function(C,j,O,q){try{mi=0;var I=Ae(C,j,O,q);return yn=null,I}catch(W){if(W===mn||W===zu)throw W;var ge=Rt(29,W,null,C.mode);return ge.lanes=q,ge.return=C,ge}finally{}}}var Nl=hh(!0),dh=hh(!1),Ga=!1;function wc(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Hc(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Xa(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function ka(e,t,a){var l=e.updateQueue;if(l===null)return null;if(l=l.shared,(Se&2)!==0){var n=l.pending;return n===null?t.next=t:(t.next=n.next,n.next=t),l.pending=t,t=Tu(e),$o(e,null,a),t}return ju(e,l,t,a),Tu(e)}function vi(e,t,a){if(t=t.updateQueue,t!==null&&(t=t.shared,(a&4194048)!==0)){var l=t.lanes;l&=e.pendingLanes,a|=l,t.lanes=a,lo(e,a)}}function qc(e,t){var a=e.updateQueue,l=e.alternate;if(l!==null&&(l=l.updateQueue,a===l)){var n=null,s=null;if(a=a.firstBaseUpdate,a!==null){do{var f={lane:a.lane,tag:a.tag,payload:a.payload,callback:null,next:null};s===null?n=s=f:s=s.next=f,a=a.next}while(a!==null);s===null?n=s=t:s=s.next=t}else n=s=t;a={baseState:l.baseState,firstBaseUpdate:n,lastBaseUpdate:s,shared:l.shared,callbacks:l.callbacks},e.updateQueue=a;return}e=a.lastBaseUpdate,e===null?a.firstBaseUpdate=t:e.next=t,a.lastBaseUpdate=t}var Lc=!1;function pi(){if(Lc){var e=dn;if(e!==null)throw e}}function gi(e,t,a,l){Lc=!1;var n=e.updateQueue;Ga=!1;var s=n.firstBaseUpdate,f=n.lastBaseUpdate,d=n.shared.pending;if(d!==null){n.shared.pending=null;var b=d,A=b.next;b.next=null,f===null?s=A:f.next=A,f=b;var D=e.alternate;D!==null&&(D=D.updateQueue,d=D.lastBaseUpdate,d!==f&&(d===null?D.firstBaseUpdate=A:d.next=A,D.lastBaseUpdate=b))}if(s!==null){var L=n.baseState;f=0,D=A=b=null,d=s;do{var _=d.lane&-536870913,N=_!==d.lane;if(N?(de&_)===_:(l&_)===_){_!==0&&_===hn&&(Lc=!0),D!==null&&(D=D.next={lane:0,tag:d.tag,payload:d.payload,callback:null,next:null});e:{var F=e,te=d;_=t;var Ae=a;switch(te.tag){case 1:if(F=te.payload,typeof F=="function"){L=F.call(Ae,L,_);break e}L=F;break e;case 3:F.flags=F.flags&-65537|128;case 0:if(F=te.payload,_=typeof F=="function"?F.call(Ae,L,_):F,_==null)break e;L=E({},L,_);break e;case 2:Ga=!0}}_=d.callback,_!==null&&(e.flags|=64,N&&(e.flags|=8192),N=n.callbacks,N===null?n.callbacks=[_]:N.push(_))}else N={lane:_,tag:d.tag,payload:d.payload,callback:d.callback,next:null},D===null?(A=D=N,b=L):D=D.next=N,f|=_;if(d=d.next,d===null){if(d=n.shared.pending,d===null)break;N=d,d=N.next,N.next=null,n.lastBaseUpdate=N,n.shared.pending=null}}while(!0);D===null&&(b=L),n.baseState=b,n.firstBaseUpdate=A,n.lastBaseUpdate=D,s===null&&(n.shared.lanes=0),Fa|=f,e.lanes=f,e.memoizedState=L}}function mh(e,t){if(typeof e!="function")throw Error(c(191,e));e.call(t)}function yh(e,t){var a=e.callbacks;if(a!==null)for(e.callbacks=null,e=0;es?s:8;var f=w.T,d={};w.T=d,lr(e,!1,t,a);try{var b=n(),A=w.S;if(A!==null&&A(d,b),b!==null&&typeof b=="object"&&typeof b.then=="function"){var D=Mp(b,l);Ei(e,t,D,Nt(e))}else Ei(e,t,l,Nt(e))}catch(L){Ei(e,t,{then:function(){},status:"rejected",reason:L},Nt())}finally{K.p=s,f!==null&&d.types!==null&&(f.types=d.types),w.T=f}}function Lp(){}function tr(e,t,a,l){if(e.tag!==5)throw Error(c(476));var n=Vh(e).queue;Kh(e,n,t,le,a===null?Lp:function(){return Jh(e),a(l)})}function Vh(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:le,baseState:le,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:pa,lastRenderedState:le},next:null};var a={};return t.next={memoizedState:a,baseState:a,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:pa,lastRenderedState:a},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Jh(e){var t=Vh(e);t.next===null&&(t=e.alternate.memoizedState),Ei(e,t.next.queue,{},Nt())}function ar(){return lt(Li)}function Fh(){return ke().memoizedState}function $h(){return ke().memoizedState}function Bp(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var a=Nt();e=Xa(a);var l=ka(t,e,a);l!==null&&(gt(l,t,a),vi(l,t,a)),t={cache:Nc()},e.payload=t;return}t=t.return}}function Qp(e,t,a){var l=Nt();a={lane:l,revertLane:0,gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},Xu(e)?Ph(t,a):(a=Sc(e,t,a,l),a!==null&&(gt(a,e,l),Ih(a,t,l)))}function Wh(e,t,a){var l=Nt();Ei(e,t,a,l)}function Ei(e,t,a,l){var n={lane:l,revertLane:0,gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null};if(Xu(e))Ph(t,n);else{var s=e.alternate;if(e.lanes===0&&(s===null||s.lanes===0)&&(s=t.lastRenderedReducer,s!==null))try{var f=t.lastRenderedState,d=s(f,a);if(n.hasEagerState=!0,n.eagerState=d,Ct(d,f))return ju(e,t,n,0),ze===null&&xu(),!1}catch{}finally{}if(a=Sc(e,t,n,l),a!==null)return gt(a,e,l),Ih(a,t,l),!0}return!1}function lr(e,t,a,l){if(l={lane:2,revertLane:wr(),gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},Xu(e)){if(t)throw Error(c(479))}else t=Sc(e,a,l,2),t!==null&>(t,e,2)}function Xu(e){var t=e.alternate;return e===se||t!==null&&t===se}function Ph(e,t){pn=Hu=!0;var a=e.pending;a===null?t.next=t:(t.next=a.next,a.next=t),e.pending=t}function Ih(e,t,a){if((a&4194048)!==0){var l=t.lanes;l&=e.pendingLanes,a|=l,t.lanes=a,lo(e,a)}}var xi={readContext:lt,use:Bu,useCallback:qe,useContext:qe,useEffect:qe,useImperativeHandle:qe,useLayoutEffect:qe,useInsertionEffect:qe,useMemo:qe,useReducer:qe,useRef:qe,useState:qe,useDebugValue:qe,useDeferredValue:qe,useTransition:qe,useSyncExternalStore:qe,useId:qe,useHostTransitionStatus:qe,useFormState:qe,useActionState:qe,useOptimistic:qe,useMemoCache:qe,useCacheRefresh:qe};xi.useEffectEvent=qe;var ed={readContext:lt,use:Bu,useCallback:function(e,t){return rt().memoizedState=[e,t===void 0?null:t],e},useContext:lt,useEffect:qh,useImperativeHandle:function(e,t,a){a=a!=null?a.concat([e]):null,Yu(4194308,4,Yh.bind(null,t,e),a)},useLayoutEffect:function(e,t){return Yu(4194308,4,e,t)},useInsertionEffect:function(e,t){Yu(4,2,e,t)},useMemo:function(e,t){var a=rt();t=t===void 0?null:t;var l=e();if(Ml){wa(!0);try{e()}finally{wa(!1)}}return a.memoizedState=[l,t],l},useReducer:function(e,t,a){var l=rt();if(a!==void 0){var n=a(t);if(Ml){wa(!0);try{a(t)}finally{wa(!1)}}}else n=t;return l.memoizedState=l.baseState=n,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:n},l.queue=e,e=e.dispatch=Qp.bind(null,se,e),[l.memoizedState,e]},useRef:function(e){var t=rt();return e={current:e},t.memoizedState=e},useState:function(e){e=$c(e);var t=e.queue,a=Wh.bind(null,se,t);return t.dispatch=a,[e.memoizedState,a]},useDebugValue:Ic,useDeferredValue:function(e,t){var a=rt();return er(a,e,t)},useTransition:function(){var e=$c(!1);return e=Kh.bind(null,se,e.queue,!0,!1),rt().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,a){var l=se,n=rt();if(ve){if(a===void 0)throw Error(c(407));a=a()}else{if(a=t(),ze===null)throw Error(c(349));(de&127)!==0||Eh(l,t,a)}n.memoizedState=a;var s={value:a,getSnapshot:t};return n.queue=s,qh(jh.bind(null,l,s,e),[e]),l.flags|=2048,bn(9,{destroy:void 0},xh.bind(null,l,s,a,t),null),a},useId:function(){var e=rt(),t=ze.identifierPrefix;if(ve){var a=ea,l=It;a=(l&~(1<<32-Tt(l)-1)).toString(32)+a,t="_"+t+"R_"+a,a=qu++,0<\/script>",s=s.removeChild(s.firstChild);break;case"select":s=typeof l.is=="string"?f.createElement("select",{is:l.is}):f.createElement("select"),l.multiple?s.multiple=!0:l.size&&(s.size=l.size);break;default:s=typeof l.is=="string"?f.createElement(n,{is:l.is}):f.createElement(n)}}s[tt]=t,s[ht]=l;e:for(f=t.child;f!==null;){if(f.tag===5||f.tag===6)s.appendChild(f.stateNode);else if(f.tag!==4&&f.tag!==27&&f.child!==null){f.child.return=f,f=f.child;continue}if(f===t)break e;for(;f.sibling===null;){if(f.return===null||f.return===t)break e;f=f.return}f.sibling.return=f.return,f=f.sibling}t.stateNode=s;e:switch(it(s,n,l),n){case"button":case"input":case"select":case"textarea":l=!!l.autoFocus;break e;case"img":l=!0;break e;default:l=!1}l&&ba(t)}}return Ue(t),pr(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,a),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==l&&ba(t);else{if(typeof l!="string"&&t.stateNode===null)throw Error(c(166));if(e=fe.current,fn(t)){if(e=t.stateNode,a=t.memoizedProps,l=null,n=at,n!==null)switch(n.tag){case 27:case 5:l=n.memoizedProps}e[tt]=t,e=!!(e.nodeValue===a||l!==null&&l.suppressHydrationWarning===!0||bm(e.nodeValue,a)),e||Qa(t,!0)}else e=rs(e).createTextNode(l),e[tt]=t,t.stateNode=e}return Ue(t),null;case 31:if(a=t.memoizedState,e===null||e.memoizedState!==null){if(l=fn(t),a!==null){if(e===null){if(!l)throw Error(c(318));if(e=t.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(c(557));e[tt]=t}else Cl(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Ue(t),e=!1}else a=Oc(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),e=!0;if(!e)return t.flags&256?(At(t),t):(At(t),null);if((t.flags&128)!==0)throw Error(c(558))}return Ue(t),null;case 13:if(l=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(n=fn(t),l!==null&&l.dehydrated!==null){if(e===null){if(!n)throw Error(c(318));if(n=t.memoizedState,n=n!==null?n.dehydrated:null,!n)throw Error(c(317));n[tt]=t}else Cl(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Ue(t),n=!1}else n=Oc(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),n=!0;if(!n)return t.flags&256?(At(t),t):(At(t),null)}return At(t),(t.flags&128)!==0?(t.lanes=a,t):(a=l!==null,e=e!==null&&e.memoizedState!==null,a&&(l=t.child,n=null,l.alternate!==null&&l.alternate.memoizedState!==null&&l.alternate.memoizedState.cachePool!==null&&(n=l.alternate.memoizedState.cachePool.pool),s=null,l.memoizedState!==null&&l.memoizedState.cachePool!==null&&(s=l.memoizedState.cachePool.pool),s!==n&&(l.flags|=2048)),a!==e&&a&&(t.child.flags|=8192),Ju(t,t.updateQueue),Ue(t),null);case 4:return Ge(),e===null&&Br(t.stateNode.containerInfo),Ue(t),null;case 10:return ya(t.type),Ue(t),null;case 19:if(Y(Xe),l=t.memoizedState,l===null)return Ue(t),null;if(n=(t.flags&128)!==0,s=l.rendering,s===null)if(n)Ti(l,!1);else{if(Le!==0||e!==null&&(e.flags&128)!==0)for(e=t.child;e!==null;){if(s=wu(e),s!==null){for(t.flags|=128,Ti(l,!1),e=s.updateQueue,t.updateQueue=e,Ju(t,e),t.subtreeFlags=0,e=a,a=t.child;a!==null;)Wo(a,e),a=a.sibling;return J(Xe,Xe.current&1|2),ve&&da(t,l.treeForkCount),t.child}e=e.sibling}l.tail!==null&&xt()>Iu&&(t.flags|=128,n=!0,Ti(l,!1),t.lanes=4194304)}else{if(!n)if(e=wu(s),e!==null){if(t.flags|=128,n=!0,e=e.updateQueue,t.updateQueue=e,Ju(t,e),Ti(l,!0),l.tail===null&&l.tailMode==="hidden"&&!s.alternate&&!ve)return Ue(t),null}else 2*xt()-l.renderingStartTime>Iu&&a!==536870912&&(t.flags|=128,n=!0,Ti(l,!1),t.lanes=4194304);l.isBackwards?(s.sibling=t.child,t.child=s):(e=l.last,e!==null?e.sibling=s:t.child=s,l.last=s)}return l.tail!==null?(e=l.tail,l.rendering=e,l.tail=e.sibling,l.renderingStartTime=xt(),e.sibling=null,a=Xe.current,J(Xe,n?a&1|2:a&1),ve&&da(t,l.treeForkCount),e):(Ue(t),null);case 22:case 23:return At(t),Qc(),l=t.memoizedState!==null,e!==null?e.memoizedState!==null!==l&&(t.flags|=8192):l&&(t.flags|=8192),l?(a&536870912)!==0&&(t.flags&128)===0&&(Ue(t),t.subtreeFlags&6&&(t.flags|=8192)):Ue(t),a=t.updateQueue,a!==null&&Ju(t,a.retryQueue),a=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),l=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(l=t.memoizedState.cachePool.pool),l!==a&&(t.flags|=2048),e!==null&&Y(Al),null;case 24:return a=null,e!==null&&(a=e.memoizedState.cache),t.memoizedState.cache!==a&&(t.flags|=2048),ya(Ze),Ue(t),null;case 25:return null;case 30:return null}throw Error(c(156,t.tag))}function Zp(e,t){switch(Cc(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return ya(Ze),Ge(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return iu(t),null;case 31:if(t.memoizedState!==null){if(At(t),t.alternate===null)throw Error(c(340));Cl()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(At(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(c(340));Cl()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return Y(Xe),null;case 4:return Ge(),null;case 10:return ya(t.type),null;case 22:case 23:return At(t),Qc(),e!==null&&Y(Al),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return ya(Ze),null;case 25:return null;default:return null}}function Td(e,t){switch(Cc(t),t.tag){case 3:ya(Ze),Ge();break;case 26:case 27:case 5:iu(t);break;case 4:Ge();break;case 31:t.memoizedState!==null&&At(t);break;case 13:At(t);break;case 19:Y(Xe);break;case 10:ya(t.type);break;case 22:case 23:At(t),Qc(),e!==null&&Y(Al);break;case 24:ya(Ze)}}function Ci(e,t){try{var a=t.updateQueue,l=a!==null?a.lastEffect:null;if(l!==null){var n=l.next;a=n;do{if((a.tag&e)===e){l=void 0;var s=a.create,f=a.inst;l=s(),f.destroy=l}a=a.next}while(a!==n)}}catch(d){Te(t,t.return,d)}}function Va(e,t,a){try{var l=t.updateQueue,n=l!==null?l.lastEffect:null;if(n!==null){var s=n.next;l=s;do{if((l.tag&e)===e){var f=l.inst,d=f.destroy;if(d!==void 0){f.destroy=void 0,n=t;var b=a,A=d;try{A()}catch(D){Te(n,b,D)}}}l=l.next}while(l!==s)}}catch(D){Te(t,t.return,D)}}function Cd(e){var t=e.updateQueue;if(t!==null){var a=e.stateNode;try{yh(t,a)}catch(l){Te(e,e.return,l)}}}function Rd(e,t,a){a.props=Dl(e.type,e.memoizedProps),a.state=e.memoizedState;try{a.componentWillUnmount()}catch(l){Te(e,t,l)}}function Ri(e,t){try{var a=e.ref;if(a!==null){switch(e.tag){case 26:case 27:case 5:var l=e.stateNode;break;case 30:l=e.stateNode;break;default:l=e.stateNode}typeof a=="function"?e.refCleanup=a(l):a.current=l}}catch(n){Te(e,t,n)}}function ta(e,t){var a=e.ref,l=e.refCleanup;if(a!==null)if(typeof l=="function")try{l()}catch(n){Te(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof a=="function")try{a(null)}catch(n){Te(e,t,n)}else a.current=null}function Od(e){var t=e.type,a=e.memoizedProps,l=e.stateNode;try{e:switch(t){case"button":case"input":case"select":case"textarea":a.autoFocus&&l.focus();break e;case"img":a.src?l.src=a.src:a.srcSet&&(l.srcset=a.srcSet)}}catch(n){Te(e,e.return,n)}}function gr(e,t,a){try{var l=e.stateNode;dg(l,e.type,a,t),l[ht]=t}catch(n){Te(e,e.return,n)}}function Ad(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&el(e.type)||e.tag===4}function br(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Ad(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&el(e.type)||e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Sr(e,t,a){var l=e.tag;if(l===5||l===6)e=e.stateNode,t?(a.nodeType===9?a.body:a.nodeName==="HTML"?a.ownerDocument.body:a).insertBefore(e,t):(t=a.nodeType===9?a.body:a.nodeName==="HTML"?a.ownerDocument.body:a,t.appendChild(e),a=a._reactRootContainer,a!=null||t.onclick!==null||(t.onclick=fa));else if(l!==4&&(l===27&&el(e.type)&&(a=e.stateNode,t=null),e=e.child,e!==null))for(Sr(e,t,a),e=e.sibling;e!==null;)Sr(e,t,a),e=e.sibling}function Fu(e,t,a){var l=e.tag;if(l===5||l===6)e=e.stateNode,t?a.insertBefore(e,t):a.appendChild(e);else if(l!==4&&(l===27&&el(e.type)&&(a=e.stateNode),e=e.child,e!==null))for(Fu(e,t,a),e=e.sibling;e!==null;)Fu(e,t,a),e=e.sibling}function _d(e){var t=e.stateNode,a=e.memoizedProps;try{for(var l=e.type,n=t.attributes;n.length;)t.removeAttributeNode(n[0]);it(t,l,a),t[tt]=e,t[ht]=a}catch(s){Te(e,e.return,s)}}var Sa=!1,Je=!1,Er=!1,zd=typeof WeakSet=="function"?WeakSet:Set,Pe=null;function Kp(e,t){if(e=e.containerInfo,Gr=vs,e=Go(e),mc(e)){if("selectionStart"in e)var a={start:e.selectionStart,end:e.selectionEnd};else e:{a=(a=e.ownerDocument)&&a.defaultView||window;var l=a.getSelection&&a.getSelection();if(l&&l.rangeCount!==0){a=l.anchorNode;var n=l.anchorOffset,s=l.focusNode;l=l.focusOffset;try{a.nodeType,s.nodeType}catch{a=null;break e}var f=0,d=-1,b=-1,A=0,D=0,L=e,_=null;t:for(;;){for(var N;L!==a||n!==0&&L.nodeType!==3||(d=f+n),L!==s||l!==0&&L.nodeType!==3||(b=f+l),L.nodeType===3&&(f+=L.nodeValue.length),(N=L.firstChild)!==null;)_=L,L=N;for(;;){if(L===e)break t;if(_===a&&++A===n&&(d=f),_===s&&++D===l&&(b=f),(N=L.nextSibling)!==null)break;L=_,_=L.parentNode}L=N}a=d===-1||b===-1?null:{start:d,end:b}}else a=null}a=a||{start:0,end:0}}else a=null;for(Xr={focusedElem:e,selectionRange:a},vs=!1,Pe=t;Pe!==null;)if(t=Pe,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,Pe=e;else for(;Pe!==null;){switch(t=Pe,s=t.alternate,e=t.flags,t.tag){case 0:if((e&4)!==0&&(e=t.updateQueue,e=e!==null?e.events:null,e!==null))for(a=0;a title"))),it(s,l,a),s[tt]=e,We(s),l=s;break e;case"link":var f=Hm("link","href",n).get(l+(a.href||""));if(f){for(var d=0;dAe&&(f=Ae,Ae=te,te=f);var C=Qo(d,te),j=Qo(d,Ae);if(C&&j&&(N.rangeCount!==1||N.anchorNode!==C.node||N.anchorOffset!==C.offset||N.focusNode!==j.node||N.focusOffset!==j.offset)){var O=L.createRange();O.setStart(C.node,C.offset),N.removeAllRanges(),te>Ae?(N.addRange(O),N.extend(j.node,j.offset)):(O.setEnd(j.node,j.offset),N.addRange(O))}}}}for(L=[],N=d;N=N.parentNode;)N.nodeType===1&&L.push({element:N,left:N.scrollLeft,top:N.scrollTop});for(typeof d.focus=="function"&&d.focus(),d=0;da?32:a,w.T=null,a=Ar,Ar=null;var s=Wa,f=Ca;if(Fe=0,Tn=Wa=null,Ca=0,(Se&6)!==0)throw Error(c(331));var d=Se;if(Se|=4,Yd(s.current),Ld(s,s.current,f,a),Se=d,Mi(0,!1),jt&&typeof jt.onPostCommitFiberRoot=="function")try{jt.onPostCommitFiberRoot($n,s)}catch{}return!0}finally{K.p=n,w.T=l,im(e,t)}}function sm(e,t,a){t=qt(a,t),t=sr(e.stateNode,t,2),e=ka(e,t,2),e!==null&&(Pn(e,2),aa(e))}function Te(e,t,a){if(e.tag===3)sm(e,e,a);else for(;t!==null;){if(t.tag===3){sm(t,e,a);break}else if(t.tag===1){var l=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof l.componentDidCatch=="function"&&($a===null||!$a.has(l))){e=qt(a,e),a=cd(2),l=ka(t,a,2),l!==null&&(rd(a,l,t,e),Pn(l,2),aa(l));break}}t=t.return}}function Mr(e,t,a){var l=e.pingCache;if(l===null){l=e.pingCache=new Fp;var n=new Set;l.set(t,n)}else n=l.get(t),n===void 0&&(n=new Set,l.set(t,n));n.has(a)||(Tr=!0,n.add(a),e=eg.bind(null,e,t,a),t.then(e,e))}function eg(e,t,a){var l=e.pingCache;l!==null&&l.delete(t),e.pingedLanes|=e.suspendedLanes&a,e.warmLanes&=~a,ze===e&&(de&a)===a&&(Le===4||Le===3&&(de&62914560)===de&&300>xt()-Pu?(Se&2)===0&&Cn(e,0):Cr|=a,jn===de&&(jn=0)),aa(e)}function cm(e,t){t===0&&(t=to()),e=jl(e,t),e!==null&&(Pn(e,t),aa(e))}function tg(e){var t=e.memoizedState,a=0;t!==null&&(a=t.retryLane),cm(e,a)}function ag(e,t){var a=0;switch(e.tag){case 31:case 13:var l=e.stateNode,n=e.memoizedState;n!==null&&(a=n.retryLane);break;case 19:l=e.stateNode;break;case 22:l=e.stateNode._retryCache;break;default:throw Error(c(314))}l!==null&&l.delete(t),cm(e,a)}function lg(e,t){return Zs(e,t)}var is=null,On=null,Dr=!1,us=!1,Ur=!1,Ia=0;function aa(e){e!==On&&e.next===null&&(On===null?is=On=e:On=On.next=e),us=!0,Dr||(Dr=!0,ig())}function Mi(e,t){if(!Ur&&us){Ur=!0;do for(var a=!1,l=is;l!==null;){if(e!==0){var n=l.pendingLanes;if(n===0)var s=0;else{var f=l.suspendedLanes,d=l.pingedLanes;s=(1<<31-Tt(42|e)+1)-1,s&=n&~(f&~d),s=s&201326741?s&201326741|1:s?s|2:0}s!==0&&(a=!0,hm(l,s))}else s=de,s=fu(l,l===ze?s:0,l.cancelPendingCommit!==null||l.timeoutHandle!==-1),(s&3)===0||Wn(l,s)||(a=!0,hm(l,s));l=l.next}while(a);Ur=!1}}function ng(){rm()}function rm(){us=Dr=!1;var e=0;Ia!==0&&yg()&&(e=Ia);for(var t=xt(),a=null,l=is;l!==null;){var n=l.next,s=fm(l,t);s===0?(l.next=null,a===null?is=n:a.next=n,n===null&&(On=a)):(a=l,(e!==0||(s&3)!==0)&&(us=!0)),l=n}Fe!==0&&Fe!==5||Mi(e),Ia!==0&&(Ia=0)}function fm(e,t){for(var a=e.suspendedLanes,l=e.pingedLanes,n=e.expirationTimes,s=e.pendingLanes&-62914561;0d)break;var D=b.transferSize,L=b.initiatorType;D&&Sm(L)&&(b=b.responseEnd,f+=D*(b"u"?null:document;function Mm(e,t,a){var l=An;if(l&&typeof t=="string"&&t){var n=wt(t);n='link[rel="'+e+'"][href="'+n+'"]',typeof a=="string"&&(n+='[crossorigin="'+a+'"]'),Nm.has(n)||(Nm.add(n),e={rel:e,crossOrigin:a,href:t},l.querySelector(n)===null&&(t=l.createElement("link"),it(t,"link",e),We(t),l.head.appendChild(t)))}}function Tg(e){Ra.D(e),Mm("dns-prefetch",e,null)}function Cg(e,t){Ra.C(e,t),Mm("preconnect",e,t)}function Rg(e,t,a){Ra.L(e,t,a);var l=An;if(l&&e&&t){var n='link[rel="preload"][as="'+wt(t)+'"]';t==="image"&&a&&a.imageSrcSet?(n+='[imagesrcset="'+wt(a.imageSrcSet)+'"]',typeof a.imageSizes=="string"&&(n+='[imagesizes="'+wt(a.imageSizes)+'"]')):n+='[href="'+wt(e)+'"]';var s=n;switch(t){case"style":s=_n(e);break;case"script":s=zn(e)}Xt.has(s)||(e=E({rel:"preload",href:t==="image"&&a&&a.imageSrcSet?void 0:e,as:t},a),Xt.set(s,e),l.querySelector(n)!==null||t==="style"&&l.querySelector(Hi(s))||t==="script"&&l.querySelector(qi(s))||(t=l.createElement("link"),it(t,"link",e),We(t),l.head.appendChild(t)))}}function Og(e,t){Ra.m(e,t);var a=An;if(a&&e){var l=t&&typeof t.as=="string"?t.as:"script",n='link[rel="modulepreload"][as="'+wt(l)+'"][href="'+wt(e)+'"]',s=n;switch(l){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":s=zn(e)}if(!Xt.has(s)&&(e=E({rel:"modulepreload",href:e},t),Xt.set(s,e),a.querySelector(n)===null)){switch(l){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(a.querySelector(qi(s)))return}l=a.createElement("link"),it(l,"link",e),We(l),a.head.appendChild(l)}}}function Ag(e,t,a){Ra.S(e,t,a);var l=An;if(l&&e){var n=$l(l).hoistableStyles,s=_n(e);t=t||"default";var f=n.get(s);if(!f){var d={loading:0,preload:null};if(f=l.querySelector(Hi(s)))d.loading=5;else{e=E({rel:"stylesheet",href:e,"data-precedence":t},a),(a=Xt.get(s))&&$r(e,a);var b=f=l.createElement("link");We(b),it(b,"link",e),b._p=new Promise(function(A,D){b.onload=A,b.onerror=D}),b.addEventListener("load",function(){d.loading|=1}),b.addEventListener("error",function(){d.loading|=2}),d.loading|=4,os(f,t,l)}f={type:"stylesheet",instance:f,count:1,state:d},n.set(s,f)}}}function _g(e,t){Ra.X(e,t);var a=An;if(a&&e){var l=$l(a).hoistableScripts,n=zn(e),s=l.get(n);s||(s=a.querySelector(qi(n)),s||(e=E({src:e,async:!0},t),(t=Xt.get(n))&&Wr(e,t),s=a.createElement("script"),We(s),it(s,"link",e),a.head.appendChild(s)),s={type:"script",instance:s,count:1,state:null},l.set(n,s))}}function zg(e,t){Ra.M(e,t);var a=An;if(a&&e){var l=$l(a).hoistableScripts,n=zn(e),s=l.get(n);s||(s=a.querySelector(qi(n)),s||(e=E({src:e,async:!0,type:"module"},t),(t=Xt.get(n))&&Wr(e,t),s=a.createElement("script"),We(s),it(s,"link",e),a.head.appendChild(s)),s={type:"script",instance:s,count:1,state:null},l.set(n,s))}}function Dm(e,t,a,l){var n=(n=fe.current)?fs(n):null;if(!n)throw Error(c(446));switch(e){case"meta":case"title":return null;case"style":return typeof a.precedence=="string"&&typeof a.href=="string"?(t=_n(a.href),a=$l(n).hoistableStyles,l=a.get(t),l||(l={type:"style",instance:null,count:0,state:null},a.set(t,l)),l):{type:"void",instance:null,count:0,state:null};case"link":if(a.rel==="stylesheet"&&typeof a.href=="string"&&typeof a.precedence=="string"){e=_n(a.href);var s=$l(n).hoistableStyles,f=s.get(e);if(f||(n=n.ownerDocument||n,f={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},s.set(e,f),(s=n.querySelector(Hi(e)))&&!s._p&&(f.instance=s,f.state.loading=5),Xt.has(e)||(a={rel:"preload",as:"style",href:a.href,crossOrigin:a.crossOrigin,integrity:a.integrity,media:a.media,hrefLang:a.hrefLang,referrerPolicy:a.referrerPolicy},Xt.set(e,a),s||Ng(n,e,a,f.state))),t&&l===null)throw Error(c(528,""));return f}if(t&&l!==null)throw Error(c(529,""));return null;case"script":return t=a.async,a=a.src,typeof a=="string"&&t&&typeof t!="function"&&typeof t!="symbol"?(t=zn(a),a=$l(n).hoistableScripts,l=a.get(t),l||(l={type:"script",instance:null,count:0,state:null},a.set(t,l)),l):{type:"void",instance:null,count:0,state:null};default:throw Error(c(444,e))}}function _n(e){return'href="'+wt(e)+'"'}function Hi(e){return'link[rel="stylesheet"]['+e+"]"}function Um(e){return E({},e,{"data-precedence":e.precedence,precedence:null})}function Ng(e,t,a,l){e.querySelector('link[rel="preload"][as="style"]['+t+"]")?l.loading=1:(t=e.createElement("link"),l.preload=t,t.addEventListener("load",function(){return l.loading|=1}),t.addEventListener("error",function(){return l.loading|=2}),it(t,"link",a),We(t),e.head.appendChild(t))}function zn(e){return'[src="'+wt(e)+'"]'}function qi(e){return"script[async]"+e}function wm(e,t,a){if(t.count++,t.instance===null)switch(t.type){case"style":var l=e.querySelector('style[data-href~="'+wt(a.href)+'"]');if(l)return t.instance=l,We(l),l;var n=E({},a,{"data-href":a.href,"data-precedence":a.precedence,href:null,precedence:null});return l=(e.ownerDocument||e).createElement("style"),We(l),it(l,"style",n),os(l,a.precedence,e),t.instance=l;case"stylesheet":n=_n(a.href);var s=e.querySelector(Hi(n));if(s)return t.state.loading|=4,t.instance=s,We(s),s;l=Um(a),(n=Xt.get(n))&&$r(l,n),s=(e.ownerDocument||e).createElement("link"),We(s);var f=s;return f._p=new Promise(function(d,b){f.onload=d,f.onerror=b}),it(s,"link",l),t.state.loading|=4,os(s,a.precedence,e),t.instance=s;case"script":return s=zn(a.src),(n=e.querySelector(qi(s)))?(t.instance=n,We(n),n):(l=a,(n=Xt.get(s))&&(l=E({},a),Wr(l,n)),e=e.ownerDocument||e,n=e.createElement("script"),We(n),it(n,"link",l),e.head.appendChild(n),t.instance=n);case"void":return null;default:throw Error(c(443,t.type))}else t.type==="stylesheet"&&(t.state.loading&4)===0&&(l=t.instance,t.state.loading|=4,os(l,a.precedence,e));return t.instance}function os(e,t,a){for(var l=a.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),n=l.length?l[l.length-1]:null,s=n,f=0;f title"):null)}function Mg(e,t,a){if(a===1||t.itemProp!=null)return!1;switch(e){case"meta":case"title":return!0;case"style":if(typeof t.precedence!="string"||typeof t.href!="string"||t.href==="")break;return!0;case"link":if(typeof t.rel!="string"||typeof t.href!="string"||t.href===""||t.onLoad||t.onError)break;switch(t.rel){case"stylesheet":return e=t.disabled,typeof t.precedence=="string"&&e==null;default:return!0}case"script":if(t.async&&typeof t.async!="function"&&typeof t.async!="symbol"&&!t.onLoad&&!t.onError&&t.src&&typeof t.src=="string")return!0}return!1}function Lm(e){return!(e.type==="stylesheet"&&(e.state.loading&3)===0)}function Dg(e,t,a,l){if(a.type==="stylesheet"&&(typeof l.media!="string"||matchMedia(l.media).matches!==!1)&&(a.state.loading&4)===0){if(a.instance===null){var n=_n(l.href),s=t.querySelector(Hi(n));if(s){t=s._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(e.count++,e=ds.bind(e),t.then(e,e)),a.state.loading|=4,a.instance=s,We(s);return}s=t.ownerDocument||t,l=Um(l),(n=Xt.get(n))&&$r(l,n),s=s.createElement("link"),We(s);var f=s;f._p=new Promise(function(d,b){f.onload=d,f.onerror=b}),it(s,"link",l),a.instance=s}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(a,t),(t=a.state.preload)&&(a.state.loading&3)===0&&(e.count++,a=ds.bind(e),t.addEventListener("load",a),t.addEventListener("error",a))}}var Pr=0;function Ug(e,t){return e.stylesheets&&e.count===0&&ys(e,e.stylesheets),0Pr?50:800)+t);return e.unsuspend=a,function(){e.unsuspend=null,clearTimeout(l),clearTimeout(n)}}:null}function ds(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)ys(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var ms=null;function ys(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,ms=new Map,t.forEach(wg,e),ms=null,ds.call(e))}function wg(e,t){if(!(t.state.loading&4)){var a=ms.get(e);if(a)var l=a.get(null);else{a=new Map,ms.set(e,a);for(var n=e.querySelectorAll("link[data-precedence],style[data-precedence]"),s=0;s"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(u){console.error(u)}}return i(),rf.exports=Ig(),rf.exports}var t0=e0();/** * react-router v7.18.1 * * Copyright (c) Remix Software Inc. @@ -55,8 +55,8 @@ Error generating stack: `+l.message+` * LICENSE.md file in the root directory of this source tree. * * @license MIT - */var qf=/^(?:[a-z][a-z0-9+.-]*:|[\\/]{2})/i,Ly=/^[\\/]{2}/;function a0(i,u){return u+i.replace(/\\/g,"/")}var ry="popstate";function fy(i){return typeof i=="object"&&i!=null&&"pathname"in i&&"search"in i&&"hash"in i&&"state"in i&&"key"in i}function l0(i={}){function u(c,o){var v;let m=(v=o.state)==null?void 0:v.masked,{pathname:y,search:g,hash:p}=m||c.location;return pf("",{pathname:y,search:g,hash:p},o.state&&o.state.usr||null,o.state&&o.state.key||"default",m?{pathname:c.location.pathname,search:c.location.search,hash:c.location.hash}:void 0)}function r(c,o){return typeof o=="string"?o:ki(o)}return i0(u,r,null,i)}function He(i,u){if(i===!1||i===null||typeof i>"u")throw new Error(u)}function ua(i,u){if(!i){typeof console<"u"&&console.warn(u);try{throw new Error(u)}catch{}}}function n0(){return Math.random().toString(36).substring(2,10)}function oy(i,u){return{usr:i.state,key:i.key,idx:u,masked:i.mask?{pathname:i.pathname,search:i.search,hash:i.hash}:void 0}}function pf(i,u,r=null,c,o){return{pathname:typeof i=="string"?i:i.pathname,search:"",hash:"",...typeof u=="string"?Vn(u):u,state:r,key:u&&u.key||c||n0(),mask:o}}function ki({pathname:i="/",search:u="",hash:r=""}){return u&&u!=="?"&&(i+=u.charAt(0)==="?"?u:"?"+u),r&&r!=="#"&&(i+=r.charAt(0)==="#"?r:"#"+r),i}function Vn(i){let u={};if(i){let r=i.indexOf("#");r>=0&&(u.hash=i.substring(r),i=i.substring(0,r));let c=i.indexOf("?");c>=0&&(u.search=i.substring(c),i=i.substring(0,c)),i&&(u.pathname=i)}return u}function i0(i,u,r,c={}){let{window:o=document.defaultView,v5Compat:m=!1}=c,y=o.history,g="POP",p=null,v=x();v==null&&(v=0,y.replaceState({...y.state,idx:v},""));function x(){return(y.state||{idx:null}).idx}function E(){g="POP";let Y=x(),B=Y==null?null:Y-v;v=Y,p&&p({action:g,location:Q.location,delta:B})}function z(Y,B){g="PUSH";let X=fy(Y)?Y:pf(Q.location,Y,B);v=x()+1;let U=oy(X,v),Z=Q.createHref(X.mask||X);try{y.pushState(U,"",Z)}catch(K){if(K instanceof DOMException&&K.name==="DataCloneError")throw K;o.location.assign(Z)}m&&p&&p({action:g,location:Q.location,delta:1})}function N(Y,B){g="REPLACE";let X=fy(Y)?Y:pf(Q.location,Y,B);v=x();let U=oy(X,v),Z=Q.createHref(X.mask||X);y.replaceState(U,"",Z),m&&p&&p({action:g,location:Q.location,delta:0})}function G(Y){return u0(o,Y)}let Q={get action(){return g},get location(){return i(o,y)},listen(Y){if(p)throw new Error("A history only accepts one active listener");return o.addEventListener(ry,E),p=Y,()=>{o.removeEventListener(ry,E),p=null}},createHref(Y){return u(o,Y)},createURL:G,encodeLocation(Y){let B=G(Y);return{pathname:B.pathname,search:B.search,hash:B.hash}},push:z,replace:N,go(Y){return y.go(Y)}};return Q}function u0(i,u,r=!1){let c="http://localhost";i&&(c=i.location.origin!=="null"?i.location.origin:i.location.href),He(c,"No window.location.(origin|href) available to create URL");let o=typeof u=="string"?u:ki(u);return o=o.replace(/ $/,"%20"),!r&&Ly.test(o)&&(o=c+o),new URL(o,c)}function Qy(i,u,r="/"){return s0(i,u,r,!1)}function s0(i,u,r,c,o){let m=typeof u=="string"?Vn(u):u,y=Ua(m.pathname||"/",r);if(y==null)return null;let g=c0(i),p=null,v=b0(y);for(let x=0;p==null&&x{let x={relativePath:v===void 0?y.path||"":v,caseSensitive:y.caseSensitive===!0,childrenIndex:g,route:y};if(x.relativePath.startsWith("/")){if(!x.relativePath.startsWith(c)&&p)return;He(x.relativePath.startsWith(c),`Absolute route path "${x.relativePath}" nested under path "${c}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),x.relativePath=x.relativePath.slice(c.length)}let E=$t([c,x.relativePath]),z=r.concat(x);y.children&&y.children.length>0&&(He(y.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${E}".`),Yy(y.children,u,z,E,p)),!(y.path==null&&!y.index)&&u.push({path:E,score:v0(E,y.index),routesMeta:z.map((N,G)=>{let[Q,Y]=Zy(N.relativePath,N.caseSensitive,G===z.length-1);return{...N,matcher:Q,compiledParams:Y}})})};return i.forEach((y,g)=>{var p;if(y.path===""||!((p=y.path)!=null&&p.includes("?")))m(y,g);else for(let v of Gy(y.path))m(y,g,!0,v)}),u}function Gy(i){let u=i.split("/");if(u.length===0)return[];let[r,...c]=u,o=r.endsWith("?"),m=r.replace(/\?$/,"");if(c.length===0)return o?[m,""]:[m];let y=Gy(c.join("/")),g=[];return g.push(...y.map(p=>p===""?m:[m,p].join("/"))),o&&g.push(...y),g.map(p=>i.startsWith("/")&&p===""?"/":p)}function r0(i){i.sort((u,r)=>u.score!==r.score?r.score-u.score:p0(u.routesMeta.map(c=>c.childrenIndex),r.routesMeta.map(c=>c.childrenIndex)))}var f0=/^:[\w-]+$/,o0=3,h0=2,d0=1,m0=10,y0=-2,hy=i=>i==="*";function v0(i,u){let r=i.split("/"),c=r.length;return r.some(hy)&&(c+=y0),u&&(c+=h0),r.filter(o=>!hy(o)).reduce((o,m)=>o+(f0.test(m)?o0:m===""?d0:m0),c)}function p0(i,u){return i.length===u.length&&i.slice(0,-1).every((c,o)=>c===u[o])?i[i.length-1]-u[u.length-1]:0}function g0(i,u,r=!1){let{routesMeta:c}=i,o={},m="/",y=[];for(let g=0;g{if(x==="*"){let G=g[z]||"";y=m.slice(0,m.length-G.length).replace(/(.)\/+$/,"$1")}const N=g[z];return E&&!N?v[x]=void 0:v[x]=(N||"").replace(/%2F/g,"/"),v},{}),pathname:m,pathnameBase:y,pattern:i}}function Zy(i,u=!1,r=!0){ua(i==="*"||!i.endsWith("*")||i.endsWith("/*"),`Route path "${i}" will be treated as if it were "${i.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${i.replace(/\*$/,"/*")}".`);let c=[],o="^"+i.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(y,g,p,v,x)=>{if(c.push({paramName:g,isOptional:p!=null}),p){let E=x.charAt(v+y.length);return E&&E!=="/"?"/([^\\/]*)":"(?:/([^\\/]*))?"}return"/([^\\/]+)"}).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return i.endsWith("*")?(c.push({paramName:"*"}),o+=i==="*"||i==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):r?o+="\\/*$":i!==""&&i!=="/"&&(o+="(?:(?=\\/|$))"),[new RegExp(o,u?void 0:"i"),c]}function b0(i){try{return i.split("/").map(u=>decodeURIComponent(u).replace(/\//g,"%2F")).join("/")}catch(u){return ua(!1,`The URL path "${i}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${u}).`),i}}function Ua(i,u){if(u==="/")return i;if(!i.toLowerCase().startsWith(u.toLowerCase()))return null;let r=u.endsWith("/")?u.length-1:u.length,c=i.charAt(r);return c&&c!=="/"?null:i.slice(r)||"/"}function S0(i,u="/"){let{pathname:r,search:c="",hash:o=""}=typeof i=="string"?Vn(i):i,m;return r?(r=Vy(r),r.startsWith("/")?m=dy(r.substring(1),"/"):m=dy(r,u)):m=u,{pathname:m,search:T0(c),hash:j0(o)}}function dy(i,u){let r=Ds(u).split("/");return i.split("/").forEach(o=>{o===".."?r.length>1&&r.pop():o!=="."&&r.push(o)}),r.length>1?r.join("/"):"/"}function df(i,u,r,c){return`Cannot include a '${i}' character in a manually specified \`to.${u}\` field [${JSON.stringify(c)}]. Please separate it out to the \`to.${r}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function E0(i){return i.filter((u,r)=>r===0||u.route.path&&u.route.path.length>0)}function Ky(i){let u=E0(i);return u.map((r,c)=>c===u.length-1?r.pathname:r.pathnameBase)}function Bf(i,u,r,c=!1){let o;typeof i=="string"?o=Vn(i):(o={...i},He(!o.pathname||!o.pathname.includes("?"),df("?","pathname","search",o)),He(!o.pathname||!o.pathname.includes("#"),df("#","pathname","hash",o)),He(!o.search||!o.search.includes("#"),df("#","search","hash",o)));let m=i===""||o.pathname==="",y=m?"/":o.pathname,g;if(y==null)g=r;else{let E=u.length-1;if(!c&&y.startsWith("..")){let z=y.split("/");for(;z[0]==="..";)z.shift(),E-=1;o.pathname=z.join("/")}g=E>=0?u[E]:"/"}let p=S0(o,g),v=y&&y!=="/"&&y.endsWith("/"),x=(m||y===".")&&r.endsWith("/");return!p.pathname.endsWith("/")&&(v||x)&&(p.pathname+="/"),p}var Vy=i=>i.replace(/[\\/]{2,}/g,"/"),$t=i=>Vy(i.join("/")),Ds=i=>i.replace(/\/+$/,""),x0=i=>Ds(i).replace(/^\/*/,"/"),T0=i=>!i||i==="?"?"":i.startsWith("?")?i:"?"+i,j0=i=>!i||i==="#"?"":i.startsWith("#")?i:"#"+i,R0=class{constructor(i,u,r,c=!1){this.status=i,this.statusText=u||"",this.internal=c,r instanceof Error?(this.data=r.toString(),this.error=r):this.data=r}};function C0(i){return i!=null&&typeof i.status=="number"&&typeof i.statusText=="string"&&typeof i.internal=="boolean"&&"data"in i}function O0(i){let u=i.map(r=>r.route.path).filter(Boolean);return $t(u)||"/"}var ky=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Jy(i,u){let r=i;if(typeof r!="string"||!qf.test(r))return{absoluteURL:void 0,isExternal:!1,to:r};let c=r,o=!1;if(ky)try{let m=new URL(window.location.href),y=Ly.test(r)?new URL(a0(r,m.protocol)):new URL(r),g=Ua(y.pathname,u);y.origin===m.origin&&g!=null?r=g+y.search+y.hash:o=!0}catch{ua(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:c,isExternal:o,to:r}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var Fy=["POST","PUT","PATCH","DELETE"];new Set(Fy);var A0=["GET",...Fy];new Set(A0);var _0=["about:","blob:","chrome:","chrome-untrusted:","content:","data:","devtools:","file:","filesystem:","javascript:"];function z0(i){try{return _0.includes(new URL(i).protocol)}catch{return!1}}var kn=C.createContext(null);kn.displayName="DataRouter";var qs=C.createContext(null);qs.displayName="DataRouterState";var $y=C.createContext(!1);function M0(){return C.useContext($y)}var Wy=C.createContext({isTransitioning:!1});Wy.displayName="ViewTransition";var N0=C.createContext(new Map);N0.displayName="Fetchers";var D0=C.createContext(null);D0.displayName="Await";var Kt=C.createContext(null);Kt.displayName="Navigation";var eu=C.createContext(null);eu.displayName="Location";var sa=C.createContext({outlet:null,matches:[],isDataRoute:!1});sa.displayName="Route";var Lf=C.createContext(null);Lf.displayName="RouteError";var Py="REACT_ROUTER_ERROR",U0="REDIRECT",w0="ROUTE_ERROR_RESPONSE";function H0(i){if(i.startsWith(`${Py}:${U0}:{`))try{let u=JSON.parse(i.slice(28));if(typeof u=="object"&&u&&typeof u.status=="number"&&typeof u.statusText=="string"&&typeof u.location=="string"&&typeof u.reloadDocument=="boolean"&&typeof u.replace=="boolean")return u}catch{}}function q0(i){if(i.startsWith(`${Py}:${w0}:{`))try{let u=JSON.parse(i.slice(40));if(typeof u=="object"&&u&&typeof u.status=="number"&&typeof u.statusText=="string")return new R0(u.status,u.statusText,u.data)}catch{}}function B0(i,{relative:u}={}){He(tu(),"useHref() may be used only in the context of a component.");let{basename:r,navigator:c}=C.useContext(Kt),{hash:o,pathname:m,search:y}=au(i,{relative:u}),g=m;return r!=="/"&&(g=m==="/"?r:$t([r,m])),c.createHref({pathname:g,search:y,hash:o})}function tu(){return C.useContext(eu)!=null}function ca(){return He(tu(),"useLocation() may be used only in the context of a component."),C.useContext(eu).location}var Iy="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function ev(i){C.useContext(Kt).static||C.useLayoutEffect(i)}function Bs(){let{isDataRoute:i}=C.useContext(sa);return i?W0():L0()}function L0(){He(tu(),"useNavigate() may be used only in the context of a component.");let i=C.useContext(kn),{basename:u,navigator:r}=C.useContext(Kt),{matches:c}=C.useContext(sa),{pathname:o}=ca(),m=JSON.stringify(Ky(c)),y=C.useRef(!1);return ev(()=>{y.current=!0}),C.useCallback((p,v={})=>{if(ua(y.current,Iy),!y.current)return;if(typeof p=="number"){r.go(p);return}let x=Bf(p,JSON.parse(m),o,v.relative==="path");i==null&&u!=="/"&&(x.pathname=x.pathname==="/"?u:$t([u,x.pathname])),(v.replace?r.replace:r.push)(x,v.state,v)},[u,r,m,o,i])}C.createContext(null);function tv(){let{matches:i}=C.useContext(sa),u=i[i.length-1];return(u==null?void 0:u.params)??{}}function au(i,{relative:u}={}){let{matches:r}=C.useContext(sa),{pathname:c}=ca(),o=JSON.stringify(Ky(r));return C.useMemo(()=>Bf(i,JSON.parse(o),c,u==="path"),[i,o,c,u])}function Q0(i,u){return av(i,u)}function av(i,u,r){var Y;He(tu(),"useRoutes() may be used only in the context of a component.");let{navigator:c}=C.useContext(Kt),{matches:o}=C.useContext(sa),m=o[o.length-1],y=m?m.params:{},g=m?m.pathname:"/",p=m?m.pathnameBase:"/",v=m&&m.route;{let B=v&&v.path||"";nv(g,!v||B.endsWith("*")||B.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${g}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. + */var qf=/^(?:[a-z][a-z0-9+.-]*:|[\\/]{2})/i,By=/^[\\/]{2}/;function a0(i,u){return u+i.replace(/\\/g,"/")}var ry="popstate";function fy(i){return typeof i=="object"&&i!=null&&"pathname"in i&&"search"in i&&"hash"in i&&"state"in i&&"key"in i}function l0(i={}){function u(c,o){var v;let m=(v=o.state)==null?void 0:v.masked,{pathname:y,search:g,hash:p}=m||c.location;return pf("",{pathname:y,search:g,hash:p},o.state&&o.state.usr||null,o.state&&o.state.key||"default",m?{pathname:c.location.pathname,search:c.location.search,hash:c.location.hash}:void 0)}function r(c,o){return typeof o=="string"?o:Vi(o)}return i0(u,r,null,i)}function He(i,u){if(i===!1||i===null||typeof i>"u")throw new Error(u)}function ua(i,u){if(!i){typeof console<"u"&&console.warn(u);try{throw new Error(u)}catch{}}}function n0(){return Math.random().toString(36).substring(2,10)}function oy(i,u){return{usr:i.state,key:i.key,idx:u,masked:i.mask?{pathname:i.pathname,search:i.search,hash:i.hash}:void 0}}function pf(i,u,r=null,c,o){return{pathname:typeof i=="string"?i:i.pathname,search:"",hash:"",...typeof u=="string"?Kn(u):u,state:r,key:u&&u.key||c||n0(),mask:o}}function Vi({pathname:i="/",search:u="",hash:r=""}){return u&&u!=="?"&&(i+=u.charAt(0)==="?"?u:"?"+u),r&&r!=="#"&&(i+=r.charAt(0)==="#"?r:"#"+r),i}function Kn(i){let u={};if(i){let r=i.indexOf("#");r>=0&&(u.hash=i.substring(r),i=i.substring(0,r));let c=i.indexOf("?");c>=0&&(u.search=i.substring(c),i=i.substring(0,c)),i&&(u.pathname=i)}return u}function i0(i,u,r,c={}){let{window:o=document.defaultView,v5Compat:m=!1}=c,y=o.history,g="POP",p=null,v=x();v==null&&(v=0,y.replaceState({...y.state,idx:v},""));function x(){return(y.state||{idx:null}).idx}function E(){g="POP";let H=x(),B=H==null?null:H-v;v=H,p&&p({action:g,location:Q.location,delta:B})}function z(H,B){g="PUSH";let X=fy(H)?H:pf(Q.location,H,B);v=x()+1;let U=oy(X,v),k=Q.createHref(X.mask||X);try{y.pushState(U,"",k)}catch(Z){if(Z instanceof DOMException&&Z.name==="DataCloneError")throw Z;o.location.assign(k)}m&&p&&p({action:g,location:Q.location,delta:1})}function M(H,B){g="REPLACE";let X=fy(H)?H:pf(Q.location,H,B);v=x();let U=oy(X,v),k=Q.createHref(X.mask||X);y.replaceState(U,"",k),m&&p&&p({action:g,location:Q.location,delta:0})}function G(H){return u0(o,H)}let Q={get action(){return g},get location(){return i(o,y)},listen(H){if(p)throw new Error("A history only accepts one active listener");return o.addEventListener(ry,E),p=H,()=>{o.removeEventListener(ry,E),p=null}},createHref(H){return u(o,H)},createURL:G,encodeLocation(H){let B=G(H);return{pathname:B.pathname,search:B.search,hash:B.hash}},push:z,replace:M,go(H){return y.go(H)}};return Q}function u0(i,u,r=!1){let c="http://localhost";i&&(c=i.location.origin!=="null"?i.location.origin:i.location.href),He(c,"No window.location.(origin|href) available to create URL");let o=typeof u=="string"?u:Vi(u);return o=o.replace(/ $/,"%20"),!r&&By.test(o)&&(o=c+o),new URL(o,c)}function Qy(i,u,r="/"){return s0(i,u,r,!1)}function s0(i,u,r,c,o){let m=typeof u=="string"?Kn(u):u,y=Ua(m.pathname||"/",r);if(y==null)return null;let g=c0(i),p=null,v=b0(y);for(let x=0;p==null&&x{let x={relativePath:v===void 0?y.path||"":v,caseSensitive:y.caseSensitive===!0,childrenIndex:g,route:y};if(x.relativePath.startsWith("/")){if(!x.relativePath.startsWith(c)&&p)return;He(x.relativePath.startsWith(c),`Absolute route path "${x.relativePath}" nested under path "${c}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),x.relativePath=x.relativePath.slice(c.length)}let E=Wt([c,x.relativePath]),z=r.concat(x);y.children&&y.children.length>0&&(He(y.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${E}".`),Yy(y.children,u,z,E,p)),!(y.path==null&&!y.index)&&u.push({path:E,score:v0(E,y.index),routesMeta:z.map((M,G)=>{let[Q,H]=ky(M.relativePath,M.caseSensitive,G===z.length-1);return{...M,matcher:Q,compiledParams:H}})})};return i.forEach((y,g)=>{var p;if(y.path===""||!((p=y.path)!=null&&p.includes("?")))m(y,g);else for(let v of Gy(y.path))m(y,g,!0,v)}),u}function Gy(i){let u=i.split("/");if(u.length===0)return[];let[r,...c]=u,o=r.endsWith("?"),m=r.replace(/\?$/,"");if(c.length===0)return o?[m,""]:[m];let y=Gy(c.join("/")),g=[];return g.push(...y.map(p=>p===""?m:[m,p].join("/"))),o&&g.push(...y),g.map(p=>i.startsWith("/")&&p===""?"/":p)}function r0(i){i.sort((u,r)=>u.score!==r.score?r.score-u.score:p0(u.routesMeta.map(c=>c.childrenIndex),r.routesMeta.map(c=>c.childrenIndex)))}var f0=/^:[\w-]+$/,o0=3,h0=2,d0=1,m0=10,y0=-2,hy=i=>i==="*";function v0(i,u){let r=i.split("/"),c=r.length;return r.some(hy)&&(c+=y0),u&&(c+=h0),r.filter(o=>!hy(o)).reduce((o,m)=>o+(f0.test(m)?o0:m===""?d0:m0),c)}function p0(i,u){return i.length===u.length&&i.slice(0,-1).every((c,o)=>c===u[o])?i[i.length-1]-u[u.length-1]:0}function g0(i,u,r=!1){let{routesMeta:c}=i,o={},m="/",y=[];for(let g=0;g{if(x==="*"){let G=g[z]||"";y=m.slice(0,m.length-G.length).replace(/(.)\/+$/,"$1")}const M=g[z];return E&&!M?v[x]=void 0:v[x]=(M||"").replace(/%2F/g,"/"),v},{}),pathname:m,pathnameBase:y,pattern:i}}function ky(i,u=!1,r=!0){ua(i==="*"||!i.endsWith("*")||i.endsWith("/*"),`Route path "${i}" will be treated as if it were "${i.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${i.replace(/\*$/,"/*")}".`);let c=[],o="^"+i.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(y,g,p,v,x)=>{if(c.push({paramName:g,isOptional:p!=null}),p){let E=x.charAt(v+y.length);return E&&E!=="/"?"/([^\\/]*)":"(?:/([^\\/]*))?"}return"/([^\\/]+)"}).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return i.endsWith("*")?(c.push({paramName:"*"}),o+=i==="*"||i==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):r?o+="\\/*$":i!==""&&i!=="/"&&(o+="(?:(?=\\/|$))"),[new RegExp(o,u?void 0:"i"),c]}function b0(i){try{return i.split("/").map(u=>decodeURIComponent(u).replace(/\//g,"%2F")).join("/")}catch(u){return ua(!1,`The URL path "${i}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${u}).`),i}}function Ua(i,u){if(u==="/")return i;if(!i.toLowerCase().startsWith(u.toLowerCase()))return null;let r=u.endsWith("/")?u.length-1:u.length,c=i.charAt(r);return c&&c!=="/"?null:i.slice(r)||"/"}function S0(i,u="/"){let{pathname:r,search:c="",hash:o=""}=typeof i=="string"?Kn(i):i,m;return r?(r=Ky(r),r.startsWith("/")?m=dy(r.substring(1),"/"):m=dy(r,u)):m=u,{pathname:m,search:j0(c),hash:T0(o)}}function dy(i,u){let r=Ds(u).split("/");return i.split("/").forEach(o=>{o===".."?r.length>1&&r.pop():o!=="."&&r.push(o)}),r.length>1?r.join("/"):"/"}function df(i,u,r,c){return`Cannot include a '${i}' character in a manually specified \`to.${u}\` field [${JSON.stringify(c)}]. Please separate it out to the \`to.${r}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function E0(i){return i.filter((u,r)=>r===0||u.route.path&&u.route.path.length>0)}function Zy(i){let u=E0(i);return u.map((r,c)=>c===u.length-1?r.pathname:r.pathnameBase)}function Lf(i,u,r,c=!1){let o;typeof i=="string"?o=Kn(i):(o={...i},He(!o.pathname||!o.pathname.includes("?"),df("?","pathname","search",o)),He(!o.pathname||!o.pathname.includes("#"),df("#","pathname","hash",o)),He(!o.search||!o.search.includes("#"),df("#","search","hash",o)));let m=i===""||o.pathname==="",y=m?"/":o.pathname,g;if(y==null)g=r;else{let E=u.length-1;if(!c&&y.startsWith("..")){let z=y.split("/");for(;z[0]==="..";)z.shift(),E-=1;o.pathname=z.join("/")}g=E>=0?u[E]:"/"}let p=S0(o,g),v=y&&y!=="/"&&y.endsWith("/"),x=(m||y===".")&&r.endsWith("/");return!p.pathname.endsWith("/")&&(v||x)&&(p.pathname+="/"),p}var Ky=i=>i.replace(/[\\/]{2,}/g,"/"),Wt=i=>Ky(i.join("/")),Ds=i=>i.replace(/\/+$/,""),x0=i=>Ds(i).replace(/^\/*/,"/"),j0=i=>!i||i==="?"?"":i.startsWith("?")?i:"?"+i,T0=i=>!i||i==="#"?"":i.startsWith("#")?i:"#"+i,C0=class{constructor(i,u,r,c=!1){this.status=i,this.statusText=u||"",this.internal=c,r instanceof Error?(this.data=r.toString(),this.error=r):this.data=r}};function R0(i){return i!=null&&typeof i.status=="number"&&typeof i.statusText=="string"&&typeof i.internal=="boolean"&&"data"in i}function O0(i){let u=i.map(r=>r.route.path).filter(Boolean);return Wt(u)||"/"}var Vy=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Jy(i,u){let r=i;if(typeof r!="string"||!qf.test(r))return{absoluteURL:void 0,isExternal:!1,to:r};let c=r,o=!1;if(Vy)try{let m=new URL(window.location.href),y=By.test(r)?new URL(a0(r,m.protocol)):new URL(r),g=Ua(y.pathname,u);y.origin===m.origin&&g!=null?r=g+y.search+y.hash:o=!0}catch{ua(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:c,isExternal:o,to:r}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var Fy=["POST","PUT","PATCH","DELETE"];new Set(Fy);var A0=["GET",...Fy];new Set(A0);var _0=["about:","blob:","chrome:","chrome-untrusted:","content:","data:","devtools:","file:","filesystem:","javascript:"];function z0(i){try{return _0.includes(new URL(i).protocol)}catch{return!1}}var Vn=R.createContext(null);Vn.displayName="DataRouter";var qs=R.createContext(null);qs.displayName="DataRouterState";var $y=R.createContext(!1);function N0(){return R.useContext($y)}var Wy=R.createContext({isTransitioning:!1});Wy.displayName="ViewTransition";var M0=R.createContext(new Map);M0.displayName="Fetchers";var D0=R.createContext(null);D0.displayName="Await";var Kt=R.createContext(null);Kt.displayName="Navigation";var eu=R.createContext(null);eu.displayName="Location";var sa=R.createContext({outlet:null,matches:[],isDataRoute:!1});sa.displayName="Route";var Bf=R.createContext(null);Bf.displayName="RouteError";var Py="REACT_ROUTER_ERROR",U0="REDIRECT",w0="ROUTE_ERROR_RESPONSE";function H0(i){if(i.startsWith(`${Py}:${U0}:{`))try{let u=JSON.parse(i.slice(28));if(typeof u=="object"&&u&&typeof u.status=="number"&&typeof u.statusText=="string"&&typeof u.location=="string"&&typeof u.reloadDocument=="boolean"&&typeof u.replace=="boolean")return u}catch{}}function q0(i){if(i.startsWith(`${Py}:${w0}:{`))try{let u=JSON.parse(i.slice(40));if(typeof u=="object"&&u&&typeof u.status=="number"&&typeof u.statusText=="string")return new C0(u.status,u.statusText,u.data)}catch{}}function L0(i,{relative:u}={}){He(tu(),"useHref() may be used only in the context of a component.");let{basename:r,navigator:c}=R.useContext(Kt),{hash:o,pathname:m,search:y}=au(i,{relative:u}),g=m;return r!=="/"&&(g=m==="/"?r:Wt([r,m])),c.createHref({pathname:g,search:y,hash:o})}function tu(){return R.useContext(eu)!=null}function ca(){return He(tu(),"useLocation() may be used only in the context of a component."),R.useContext(eu).location}var Iy="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function ev(i){R.useContext(Kt).static||R.useLayoutEffect(i)}function Ls(){let{isDataRoute:i}=R.useContext(sa);return i?W0():B0()}function B0(){He(tu(),"useNavigate() may be used only in the context of a component.");let i=R.useContext(Vn),{basename:u,navigator:r}=R.useContext(Kt),{matches:c}=R.useContext(sa),{pathname:o}=ca(),m=JSON.stringify(Zy(c)),y=R.useRef(!1);return ev(()=>{y.current=!0}),R.useCallback((p,v={})=>{if(ua(y.current,Iy),!y.current)return;if(typeof p=="number"){r.go(p);return}let x=Lf(p,JSON.parse(m),o,v.relative==="path");i==null&&u!=="/"&&(x.pathname=x.pathname==="/"?u:Wt([u,x.pathname])),(v.replace?r.replace:r.push)(x,v.state,v)},[u,r,m,o,i])}R.createContext(null);function tv(){let{matches:i}=R.useContext(sa),u=i[i.length-1];return(u==null?void 0:u.params)??{}}function au(i,{relative:u}={}){let{matches:r}=R.useContext(sa),{pathname:c}=ca(),o=JSON.stringify(Zy(r));return R.useMemo(()=>Lf(i,JSON.parse(o),c,u==="path"),[i,o,c,u])}function Q0(i,u){return av(i,u)}function av(i,u,r){var H;He(tu(),"useRoutes() may be used only in the context of a component.");let{navigator:c}=R.useContext(Kt),{matches:o}=R.useContext(sa),m=o[o.length-1],y=m?m.params:{},g=m?m.pathname:"/",p=m?m.pathnameBase:"/",v=m&&m.route;{let B=v&&v.path||"";nv(g,!v||B.endsWith("*")||B.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${g}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. -Please change the parent to .`)}let x=ca(),E;if(u){let B=typeof u=="string"?Vn(u):u;He(p==="/"||((Y=B.pathname)==null?void 0:Y.startsWith(p)),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${p}" but pathname "${B.pathname}" was given in the \`location\` prop.`),E=B}else E=x;let z=E.pathname||"/",N=z;if(p!=="/"){let B=p.replace(/^\//,"").split("/");N="/"+z.replace(/^\//,"").split("/").slice(B.length).join("/")}let G=r&&r.state.matches.length?r.state.matches.map(B=>Object.assign(B,{route:r.manifest[B.route.id]||B.route})):Qy(i,{pathname:N});ua(v||G!=null,`No routes matched location "${E.pathname}${E.search}${E.hash}" `),ua(G==null||G[G.length-1].route.element!==void 0||G[G.length-1].route.Component!==void 0||G[G.length-1].route.lazy!==void 0,`Matched leaf route at location "${E.pathname}${E.search}${E.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let Q=K0(G&&G.map(B=>Object.assign({},B,{params:Object.assign({},y,B.params),pathname:$t([p,c.encodeLocation?c.encodeLocation(B.pathname.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:B.pathname]),pathnameBase:B.pathnameBase==="/"?p:$t([p,c.encodeLocation?c.encodeLocation(B.pathnameBase.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:B.pathnameBase])})),o,r);return u&&Q?C.createElement(eu.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",mask:void 0,...E},navigationType:"POP"}},Q):Q}function Y0(){let i=$0(),u=C0(i)?`${i.status} ${i.statusText}`:i instanceof Error?i.message:JSON.stringify(i),r=i instanceof Error?i.stack:null,c="rgba(200,200,200, 0.5)",o={padding:"0.5rem",backgroundColor:c},m={padding:"2px 4px",backgroundColor:c},y=null;return console.error("Error handled by React Router default ErrorBoundary:",i),y=C.createElement(C.Fragment,null,C.createElement("p",null,"💿 Hey developer 👋"),C.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",C.createElement("code",{style:m},"ErrorBoundary")," or"," ",C.createElement("code",{style:m},"errorElement")," prop on your route.")),C.createElement(C.Fragment,null,C.createElement("h2",null,"Unexpected Application Error!"),C.createElement("h3",{style:{fontStyle:"italic"}},u),r?C.createElement("pre",{style:o},r):null,y)}var G0=C.createElement(Y0,null),lv=class extends C.Component{constructor(i){super(i),this.state={location:i.location,revalidation:i.revalidation,error:i.error}}static getDerivedStateFromError(i){return{error:i}}static getDerivedStateFromProps(i,u){return u.location!==i.location||u.revalidation!=="idle"&&i.revalidation==="idle"?{error:i.error,location:i.location,revalidation:i.revalidation}:{error:i.error!==void 0?i.error:u.error,location:u.location,revalidation:i.revalidation||u.revalidation}}componentDidCatch(i,u){this.props.onError?this.props.onError(i,u):console.error("React Router caught the following error during render",i)}render(){let i=this.state.error;if(this.context&&typeof i=="object"&&i&&"digest"in i&&typeof i.digest=="string"){const r=q0(i.digest);r&&(i=r)}let u=i!==void 0?C.createElement(sa.Provider,{value:this.props.routeContext},C.createElement(Lf.Provider,{value:i,children:this.props.component})):this.props.children;return this.context?C.createElement(X0,{error:i},u):u}};lv.contextType=$y;var mf=new WeakMap;function X0({children:i,error:u}){let{basename:r}=C.useContext(Kt);if(typeof u=="object"&&u&&"digest"in u&&typeof u.digest=="string"){let c=H0(u.digest);if(c){let o=mf.get(u);if(o)throw o;let m=Jy(c.location,r),y=m.absoluteURL||m.to;if(z0(y))throw new Error("Invalid redirect location");if(ky&&!mf.get(u))if(m.isExternal||c.reloadDocument)window.location.href=y;else{const g=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(m.to,{replace:c.replace}));throw mf.set(u,g),g}return C.createElement("meta",{httpEquiv:"refresh",content:`0;url=${y}`})}}return i}function Z0({routeContext:i,match:u,children:r}){let c=C.useContext(kn);return c&&c.static&&c.staticContext&&(u.route.errorElement||u.route.ErrorBoundary)&&(c.staticContext._deepestRenderedBoundaryId=u.route.id),C.createElement(sa.Provider,{value:i},r)}function K0(i,u=[],r){let c=r==null?void 0:r.state;if(i==null){if(!c)return null;if(c.errors)i=c.matches;else if(u.length===0&&!c.initialized&&c.matches.length>0)i=c.matches;else return null}let o=i,m=c==null?void 0:c.errors;if(m!=null){let x=o.findIndex(E=>E.route.id&&(m==null?void 0:m[E.route.id])!==void 0);He(x>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(m).join(",")}`),o=o.slice(0,Math.min(o.length,x+1))}let y=!1,g=-1;if(r&&c){y=c.renderFallback;for(let x=0;x=0?o=o.slice(0,g+1):o=[o[0]];break}}}}let p=r==null?void 0:r.onError,v=c&&p?(x,E)=>{var z,N;p(x,{location:c.location,params:((N=(z=c.matches)==null?void 0:z[0])==null?void 0:N.params)??{},pattern:O0(c.matches),errorInfo:E})}:void 0;return o.reduceRight((x,E,z)=>{let N,G=!1,Q=null,Y=null;c&&(N=m&&E.route.id?m[E.route.id]:void 0,Q=E.route.errorElement||G0,y&&(g<0&&z===0?(nv("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),G=!0,Y=null):g===z&&(G=!0,Y=E.route.hydrateFallbackElement||null)));let B=u.concat(o.slice(0,z+1)),X=()=>{let U;return N?U=Q:G?U=Y:E.route.Component?U=C.createElement(E.route.Component,null):E.route.element?U=E.route.element:U=x,C.createElement(Z0,{match:E,routeContext:{outlet:x,matches:B,isDataRoute:c!=null},children:U})};return c&&(E.route.ErrorBoundary||E.route.errorElement||z===0)?C.createElement(lv,{location:c.location,revalidation:c.revalidation,component:Q,error:N,children:X(),routeContext:{outlet:null,matches:B,isDataRoute:!0},onError:v}):X()},null)}function Qf(i){return`${i} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function V0(i){let u=C.useContext(kn);return He(u,Qf(i)),u}function k0(i){let u=C.useContext(qs);return He(u,Qf(i)),u}function J0(i){let u=C.useContext(sa);return He(u,Qf(i)),u}function Yf(i){let u=J0(i),r=u.matches[u.matches.length-1];return He(r.route.id,`${i} can only be used on routes that contain a unique "id"`),r.route.id}function F0(){return Yf("useRouteId")}function $0(){var c;let i=C.useContext(Lf),u=k0("useRouteError"),r=Yf("useRouteError");return i!==void 0?i:(c=u.errors)==null?void 0:c[r]}function W0(){let{router:i}=V0("useNavigate"),u=Yf("useNavigate"),r=C.useRef(!1);return ev(()=>{r.current=!0}),C.useCallback(async(o,m={})=>{ua(r.current,Iy),r.current&&(typeof o=="number"?await i.navigate(o):await i.navigate(o,{fromRouteId:u,...m}))},[i,u])}var my={};function nv(i,u,r){!u&&!my[i]&&(my[i]=!0,ua(!1,r))}C.memo(P0);function P0({routes:i,manifest:u,future:r,state:c,isStatic:o,onError:m}){return av(i,void 0,{manifest:u,state:c,isStatic:o,onError:m})}function Ki(i){He(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function I0({basename:i="/",children:u=null,location:r,navigationType:c="POP",navigator:o,static:m=!1,useTransitions:y}){He(!tu(),"You cannot render a inside another . You should never have more than one in your app.");let g=i.replace(/^\/*/,"/"),p=C.useMemo(()=>({basename:g,navigator:o,static:m,useTransitions:y,future:{}}),[g,o,m,y]);typeof r=="string"&&(r=Vn(r));let{pathname:v="/",search:x="",hash:E="",state:z=null,key:N="default",mask:G}=r,Q=C.useMemo(()=>{let Y=Ua(v,g);return Y==null?null:{location:{pathname:Y,search:x,hash:E,state:z,key:N,mask:G},navigationType:c}},[g,v,x,E,z,N,c,G]);return ua(Q!=null,` is not able to match the URL "${v}${x}${E}" because it does not start with the basename, so the won't render anything.`),Q==null?null:C.createElement(Kt.Provider,{value:p},C.createElement(eu.Provider,{children:u,value:Q}))}function eb({children:i,location:u}){return Q0(gf(i),u)}function gf(i,u=[]){let r=[];return C.Children.forEach(i,(c,o)=>{if(!C.isValidElement(c))return;let m=[...u,o];if(c.type===C.Fragment){r.push.apply(r,gf(c.props.children,m));return}He(c.type===Ki,`[${typeof c.type=="string"?c.type:c.type.name}] is not a component. All component children of must be a or `),He(!c.props.index||!c.props.children,"An index route cannot have child routes.");let y={id:c.props.id||m.join("-"),caseSensitive:c.props.caseSensitive,element:c.props.element,Component:c.props.Component,index:c.props.index,path:c.props.path,middleware:c.props.middleware,loader:c.props.loader,action:c.props.action,hydrateFallbackElement:c.props.hydrateFallbackElement,HydrateFallback:c.props.HydrateFallback,errorElement:c.props.errorElement,ErrorBoundary:c.props.ErrorBoundary,hasErrorBoundary:c.props.hasErrorBoundary===!0||c.props.ErrorBoundary!=null||c.props.errorElement!=null,shouldRevalidate:c.props.shouldRevalidate,handle:c.props.handle,lazy:c.props.lazy};c.props.children&&(y.children=gf(c.props.children,m)),r.push(y)}),r}var Os="get",As="application/x-www-form-urlencoded";function Ls(i){return typeof HTMLElement<"u"&&i instanceof HTMLElement}function tb(i){return Ls(i)&&i.tagName.toLowerCase()==="button"}function ab(i){return Ls(i)&&i.tagName.toLowerCase()==="form"}function lb(i){return Ls(i)&&i.tagName.toLowerCase()==="input"}function nb(i){return!!(i.metaKey||i.altKey||i.ctrlKey||i.shiftKey)}function ib(i,u){return i.button===0&&(!u||u==="_self")&&!nb(i)}var js=null;function ub(){if(js===null)try{new FormData(document.createElement("form"),0),js=!1}catch{js=!0}return js}var sb=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function yf(i){return i!=null&&!sb.has(i)?(ua(!1,`"${i}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${As}"`),null):i}function cb(i,u){let r,c,o,m,y;if(ab(i)){let g=i.getAttribute("action");c=g?Ua(g,u):null,r=i.getAttribute("method")||Os,o=yf(i.getAttribute("enctype"))||As,m=new FormData(i)}else if(tb(i)||lb(i)&&(i.type==="submit"||i.type==="image")){let g=i.form;if(g==null)throw new Error('Cannot submit a )}{!matches.length&&

No matching command.

}

run esc close

} function Shell({children,onLogout}:{children:React.ReactNode;onLogout:()=>void}){const where=useLocation(),nav=useNavigate(),[palette,setPalette]=useState(false),[accountOpen,setAccountOpen]=useState(false);const overview=useQuery({queryKey:['overview'],queryFn:api.overview,refetchInterval:5000});const sessions=overview.data?.sessions?.length??0;useEffect(()=>{const key=(e:KeyboardEvent)=>{const editable=e.target instanceof HTMLInputElement||e.target instanceof HTMLTextAreaElement;if((e.metaKey||e.ctrlKey)&&e.key.toLowerCase()==='k'){e.preventDefault();setPalette(true)}if(e.key==='n'&&!e.metaKey&&!e.ctrlKey&&!editable){nav('/');window.dispatchEvent(new Event('orchestra:new-task'))}if(e.key==='r'&&!e.metaKey&&!e.ctrlKey&&!editable)overview.refetch()};window.addEventListener('keydown',key);return()=>window.removeEventListener('keydown',key)},[nav,overview]);return
Orchestra {where.pathname==='/'?'dispatch board':where.pathname==='/workers'?'worker pool':'task record'}
SESSIONS {sessions}SYNC 5s
{accountOpen&&
Browser session active
}
{children}{palette&&setPalette(false)}/>}
} function taskExplanation(task:Task,overview:Overview){const session=(overview.sessions??[]).find(s=>s.capture?.task_id===task.id);if(task.state==='queued'){const online=(overview.workers??[]).filter(w=>w.online);return online.length?`Waiting for a worker to lease it · ${online.length} worker${online.length===1?'':'s'} online`:'No registered worker is currently reachable'}if(task.state==='leased'){if(session?.pending_approval)return 'Waiting for an operator approval';if(task.lease&&new Date(task.lease.until){task.title||task.id}{task.project}{task.id.slice(-5)}{taskExplanation(task,overview)}} -function Board({tasks,overview,empty}:{tasks:Task[];overview:Overview;empty:string}){const blocked=tasks.filter(t=>t.state==='blocked').sort((a,b)=>new Date(a.blocked_at||0).getTime()-new Date(b.blocked_at||0).getTime());const groups=new Map;for(const task of blocked){const reason=task.block_reason||'unknown';groups.set(reason,[...(groups.get(reason)||[]),task])}if(!tasks.length)return

No matching work

{empty}

;const boardStates=states.filter(state=>tasks.some(t=>t.state===state));return <>
{boardStates.map(state=>{const lane=tasks.filter(t=>t.state===state);return
{label[state]}{lane.length}
{lane.map(t=>)}
})}
{blocked.length>0&&

Needs attention

Blocked work is grouped by diagnosis and ordered oldest first.

{blocked.length} blocked
{[...groups.entries()].map(([reason,group])=>
{blockLabel[reason]}{group.length}
{group.map(task=>)}
)}
}} +function TaskCard({task,overview,showBlockedAt=false}:{task:Task;overview:Overview;showBlockedAt?:boolean}){const blockedAt=recordedTime(task.blocked_at);return {task.title||task.id}{task.project}{task.id.slice(-5)}{taskExplanation(task,overview)}{showBlockedAt&&blockedAt&&}} +function Board({tasks,overview,empty}:{tasks:Task[];overview:Overview;empty:string}){const blocked=tasks.filter(t=>t.state==='blocked').sort((a,b)=>new Date(a.blocked_at||0).getTime()-new Date(b.blocked_at||0).getTime());const groups=new Map;for(const task of blocked){const reason=task.block_reason||'unknown';groups.set(reason,[...(groups.get(reason)||[]),task])}if(!tasks.length)return

No matching work

{empty}

;const boardStates=states.filter(state=>tasks.some(t=>t.state===state));return <>
{boardStates.map(state=>{const lane=tasks.filter(t=>t.state===state);return
{label[state]}{lane.length}
{lane.map(t=>)}
})}
{blocked.length>0&&

Needs attention

Blocked work is grouped by diagnosis and ordered oldest first.

{blocked.length} blocked
{[...groups.entries()].map(([reason,group])=>
{blockLabel[reason]}{group.length}
{group.map(task=>)}
)}
}} function OverviewPage(){const q=useQuery({queryKey:['overview'],queryFn:api.overview,refetchInterval:5000}),[createOpen,setCreateOpen]=useState(false),[view,setView]=useState<'active'|'history'|'all'>('active'),[project,setProject]=useState(''),[search,setSearch]=useState('');useEffect(()=>{const open=()=>setCreateOpen(true);window.addEventListener('orchestra:new-task',open);return()=>window.removeEventListener('orchestra:new-task',open)},[]);if(q.isLoading)return

Dispatch board

Fetching queue state from the coordinator…

;if(q.error)return
Queue unavailable: {String(q.error)}
;const d=q.data!,sessions=d.sessions??[],active=d.tasks.filter(t=>t.state==='leased').length,attention=d.tasks.filter(t=>t.state==='blocked'||t.state==='failed').length,approvals=new Set(sessions.filter(s=>s.pending_approval).map(s=>s.capture?.task_id)),projects=[...new Set(d.tasks.map(t=>t.project).filter(Boolean))].sort(),term=search.trim().toLowerCase(),filtered=d.tasks.filter(t=>(!project||t.project===project)&&(!term||[t.id,t.title,t.description,t.project,t.blocker].filter(Boolean).join(' ').toLowerCase().includes(term))).filter(t=>view==='all'||view==='active'?activeStates.includes(t.state):!activeStates.includes(t.state)),history=d.tasks.filter(t=>!activeStates.includes(t.state)).length,empty=view==='active'?'There is no live work right now. Completed and failed records are available in History.':view==='history'?'No completed or failed records match these filters.':'No task records match these filters.';return

Agent dispatch

Keep the work moving.

Live task state across every connected harness. The board refreshes every five seconds.

Tasks{d.tasks.length}
In session{active}
Needs attention{attention}
Awaiting approval{approvals.size}

Task flow

{view==='active'?'Live, waiting, and needs-attention work.':view==='history'?'Completed and failed task records.':'All matching task records.'}

SYNCED {new Date(d.updated_at).toLocaleTimeString()}
setSearch(e.target.value)} placeholder="Search tasks"/>
{createOpen&&setCreateOpen(false)}/>}
} @@ -40,7 +44,7 @@ function taskDetailExplanation(d:Detail){if(d.session?.pending_approval)return ' function Workers(){const q=useQuery({queryKey:['overview'],queryFn:api.overview,refetchInterval:5000});if(q.isLoading)return
Fetching worker heartbeats…
;if(q.error)return
Worker pool unavailable: {String(q.error)}
;const d=q.data!,workers=d.workers;const herdr=(status?:string)=>status==='reachable'?'local herdr reachable':status==='unreachable'?'local herdr unreachable':'local herdr not yet checked';return
← Dispatch board

Worker pool

Available capacity.

{workers.filter(w=>w.online).length} of {workers.length} registered workers are reachable right now.

{workers.length?<>

Heartbeat proves the worker can reach Orchestra. Local herdr status is separately reported by that worker; it is never inferred from legacy coordinator TCP probes.

{workers.map(w=>)}
WorkerHeartbeatLocal herdrActive workLast errorLast heartbeat
{w.id} {w.online?'reachable':'overdue'}{herdr(w.health?.herdr_status)}{date(w.health?.checked_at)}{w.health?.active_task_id?<>{w.health.active_task_id}{w.health.active_pane_id||'pane unknown'}:'idle'}{w.health?.last_error?<>{w.health.last_error}{date(w.health.error_at)}:'—'}{date(w.last_seen)}
:

No workers registered

Connect a worker to begin leasing queued tasks.

}
} function Artifact(){const {ref=''}=useParams();const q=useQuery({queryKey:['artifact',ref],queryFn:()=>api.artifact(ref)});return
← Dispatch board

Evidence artifact

Recorded output.

{ref}

{q.isLoading?

Retrieving artifact…

:q.error?

{String(q.error)}

:
{q.data}
}
} -function Login({onAuthenticated,message}:{onAuthenticated:()=>void;message?:string}){const [token,setToken]=useState(''),[error,setError]=useState(''),[pending,setPending]=useState(false);const submit=async(e:React.FormEvent)=>{e.preventDefault();setError('');setPending(true);try{await api.login(token);setToken('');onAuthenticated()}catch(err){setError(String(err))}finally{setPending(false)}};return

Orchestra control plane

Sign in

Enter the Web token to open a secure browser session.

{message&&

{message}

}{error&&{error}}
} +function Login({onAuthenticated,message}:{onAuthenticated:()=>void;message?:string}){const input=useRef(null),[username,setUsername]=useState(''),[password,setPassword]=useState(''),[error,setError]=useState(''),[pending,setPending]=useState(false),[visible,setVisible]=useState(false),[capsLock,setCapsLock]=useState(false);useEffect(()=>input.current?.focus(),[]);const submit=async(e:React.FormEvent)=>{e.preventDefault();if(!username||!password){setError('Enter your username and password to continue.');(!username?input.current:document.getElementById('web-password'))?.focus();return}setError('');setPending(true);try{await api.login(username,password);setPassword('');onAuthenticated()}catch{setError('That username or password was not accepted. Check both and try again.')}finally{setPending(false)}};return