diff --git a/.gitattributes b/.gitattributes index 72f22046..28717a56 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,5 @@ internal/web/dist/** linguist-generated=true -whitespace assets/screenshots/** linguist-generated=true tools/screenshots/fonts/*.ttf linguist-vendored=true +web/src/contracts.generated.ts text eol=lf linguist-generated=true +web/tests/projections.generated.json text eol=lf linguist-generated=true diff --git a/Makefile b/Makefile index 374e9461..7a6f1525 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build test integration-test check fmt vet release-snapshot web-build web-test screenshots screenshots-check +.PHONY: build test integration-test check fmt vet release-snapshot web-build web-test screenshots screenshots-check web-contracts # A tagged checkout gets the nearest semantic Git description. Repositories # without a reachable tag leave this empty so Go's embedded VCS revision and @@ -42,3 +42,6 @@ screenshots: # No browser needed: compare current terminal cells, versions and image hashes. screenshots-check: node web/scripts/screenshots.mjs --check + +web-contracts: + CODEXOMETER_WEB_GENERATE=1 go test ./internal/web -run '^TestBrowser(Contract|ProjectionFixtures)$$' -count=1 diff --git a/README.md b/README.md index 1290eb6f..25a5c0c4 100644 --- a/README.md +++ b/README.md @@ -3703,6 +3703,39 @@ OpenAI telemetry nor improves quota-estimation accuracy. ## Development +### Shared logic and browser contracts + +Business rules belong in the Go core, not in a second renderer-specific +implementation. Backend quota windows use `codex.Meter.Key()` (limit ID, kind +and primary/secondary slot), never a mutable label. Session status and bounded +counter samples also share Go projections; each interface retains its own +selection, measurement baseline and display preferences. + +`internal/quotaestimate` owns process-local API-EQ learning independently of +terminal geometry, colours and translations. `internal/quotagraph` supplies +coverage, regression, clipping and numeric Pace geometry to both interfaces. +Browser Pace geometry is tied to the quota observation's timestamp, not a +separately advancing browser clock; live reset timelines and countdown labels +still advance normally. Quota health, reset gauges and graph geometry share +elapsed-cycle calculations. Usage dimensions, historical warning thresholds, +breakdown ordering, units and relative bar scales are owned in Go, including +fractional totals and the distinction between missing and zero data. + +Browser types, the local slash-command catalogue and numerical browser fixtures +are generated from the **browser-safe** Go contracts. Refresh them after a +contract or projector change: + +```sh +make web-contracts +``` + +Ordinary Go tests check these generated files without rewriting them. Generate +from the safe projections, never raw session/client structs containing approval +capabilities or credentials. Chromium tests consume Go-produced numerical +fixtures; they do not implement another version of the calculations. No new +runtime dependency or framework is required. Terminal and SVG drawing remain +separate, as do browser-only security checks and transport-specific lifecycles. + Format, test, and vet the project: ```sh diff --git a/assets/screenshots/manifest.json b/assets/screenshots/manifest.json index 17df9cdc..989621d5 100644 --- a/assets/screenshots/manifest.json +++ b/assets/screenshots/manifest.json @@ -2,7 +2,7 @@ "version": "0.27.1", "synthetic": true, "sources": { - "internal/ui/documentation_screenshots_test.go": "4d7f8607c62e22dac4c70fc66927a3bddc91dd2d6dd15dab2c6039c16947dc67", + "internal/ui/documentation_screenshots_test.go": "015943f799500586ab1d77f25f056ee22f77b5d7a7110a5101450aeef2d57878", "web/scripts/screenshots.mjs": "fe8f5b91a037ff833ee646f999d0bd2eb17e8d8a6bd333d944e57eb68e54f680", "web/package-lock.json": "d303563499dd89b42c77cf5ad10df0a2325d77148a7c344d716ae580a8525eea", "tools/screenshots/fonts/DejaVuSansMono.ttf": "c805f9436dbc268644c1d9584f01a601a653e028e08fd74b9b949f6cf8304d88", diff --git a/internal/codex/session_projection_test.go b/internal/codex/session_projection_test.go new file mode 100644 index 00000000..0422acfb --- /dev/null +++ b/internal/codex/session_projection_test.go @@ -0,0 +1,158 @@ +package codex + +import ( + "reflect" + "testing" + "time" +) + +func TestUsageWarningLevelIsSharedWithPeriodProjections(t *testing.T) { + for _, tc := range []struct { + known bool + value float64 + want string + }{ + {false, 100, "unknown"}, {true, -.25, "normal"}, {true, 0, "normal"}, + {true, 79.99, "normal"}, {true, 80, "near"}, {true, 99.99, "near"}, + {true, 100, "full"}, {true, 125, "full"}, + } { + if got := UsageWarningLevel(tc.known, tc.value); got != tc.want { + t.Fatalf("known=%v value=%v level=%q want=%q", tc.known, tc.value, got, tc.want) + } + period := PlanUsagePeriod{} + if tc.known { + period.UsedBasisPoints = new(tc.value * 100) + } + for _, dimension := range UsageDimensions() { + if got := WindowUsageDistribution(period, dimension).Level; got != tc.want { + t.Fatalf("%s period level=%q want=%q", dimension, got, tc.want) + } + } + } + if d := DailyUsageDistribution(UsageBreakdownDay{Total: 125}, "surface"); d.Level != "" { + t.Fatal("daily relative units were classified as quota percentages", d) + } +} + +func TestSessionStatusProjection(t *testing.T) { + for _, test := range []struct { + active, working bool + attention SessionAttention + code, want string + }{ + {true, false, SessionAttentionNone, "", "IDLE"}, {true, true, SessionAttentionNone, "", "WORKING"}, + {false, false, SessionAttentionNone, "", "INACTIVE"}, + {true, true, SessionAttentionInput, "", "INPUT NEEDED"}, {true, true, SessionAttentionApproval, "", "APPROVAL NEEDED"}, + {true, false, SessionAttentionCheck, "", "CHECK SESSION"}, {true, false, SessionAttentionComplete, "", "TURN COMPLETE"}, + {true, false, SessionAttentionError, "serverOverloaded", "MODEL BUSY"}, + {true, false, SessionAttentionError, "modelNotFound", "MODEL UNAVAILABLE"}, + {true, false, SessionAttentionError, "policyCapacityUnavailable", "CAPACITY RESTRICTED"}, + {true, false, SessionAttentionError, "future", "TURN FAILED"}, + } { + if got := SessionStatus(test.active, test.working, test.attention, SessionContext{FailureCode: test.code}); got != test.want { + t.Fatalf("%+v: %q", test, got) + } + } + if SessionAttentionLabel(SessionAttentionNone, SessionContext{}) != "" { + t.Fatal("no attention invented an input request") + } +} + +func TestMeterKeysIgnoreLabelsAndDistinguishEverySlot(t *testing.T) { + duration := int64(300) + label := "Shared label" + w := Window{WindowDurationMins: &duration} + q := Snapshot{RateLimitsByLimitID: map[string]RateLimitSnapshot{"a": {LimitName: &label, Primary: &w, Secondary: &w}, "b": {LimitName: &label, Primary: &w}}} + seen := map[MeterKey]bool{} + for _, meter := range q.Meters() { + if seen[meter.Key()] { + t.Fatal("identity collision", meter.Key()) + } + seen[meter.Key()] = true + } + q.RateLimitsByLimitID["a"] = RateLimitSnapshot{Secondary: &w} + if key := q.Meters()[0].Key(); !seen[key] || key.Slot != 1 { + t.Fatal("secondary identity changed when primary disappeared", key) + } +} + +func TestUsageDistributionFractionalUnitsAndMissingData(t *testing.T) { + day := UsageBreakdownDay{Total: .1, Groups: map[string]map[string]float64{"surface": {"cli": .05, "desktop": .05}}} + d := DailyUsageDistribution(day, "surface") + if d.Peak != .1 || d.Rows[0].Value/d.Peak != .5 || d.Rows[0].Name != "cli" { + t.Fatal("fractional scale/order changed", d) + } + missing := DailyUsageDistribution(UsageBreakdownDay{}, "surface") + empty := DailyUsageDistribution(UsageBreakdownDay{Groups: map[string]map[string]float64{"surface": {}}}, "surface") + if missing.KnownEmpty || !empty.KnownEmpty { + t.Fatal("missing was treated as zero") + } + used := 12500.0 + period := PlanUsagePeriod{UsedBasisPoints: &used, Breakdowns: []PlanUsageBreakdown{ + {Dimension: "thread_source", Rows: []PlanUsageValue{{Key: "agent", BasisPoints: 10000}}}, + {Dimension: "thread_source", Rows: []PlanUsageValue{{Key: "agent", BasisPoints: 2500}}}, + }} + p := WindowUsageDistribution(period, "feature") + if *p.Total != 125 || p.Peak != 125 || p.Rows[0].Value != 125 { + t.Fatal("window units or aggregation changed", p) + } + raw := &UsageReports{Daily: &DailyUsageReport{Days: []UsageBreakdownDay{day}}, Plan: &PlanUsageReport{Periods: []PlanUsagePeriod{period}}} + projected := ProjectUsageReports(raw) + if raw.Daily.Days[0].Views != nil || raw.Plan.Periods[0].Views != nil || !reflect.DeepEqual(projected.Daily.Days[0].Views["surface"], d) { + t.Fatal("projection mutated source or disagreed with renderer") + } +} + +func TestUsageSamplesShareBoundsAndElapsedIntervals(t *testing.T) { + now := time.Unix(1800000000, 0) + var samples []UsageSample + for i := 0; i < 4; i++ { + samples = CaptureUsageSample(samples, int64((i+1)*10), int64(i*10), now, now.Add(time.Duration(i+1)*30*time.Second), 2) + } + if len(samples) != 2 || samples[0].At != now.Add(90*time.Second) || samples[1].Duration != 30*time.Second || samples[1].Tokens != 10 { + t.Fatal(samples) + } + samples = CaptureUsageSample(samples, 1, 100, now, now.Add(time.Minute), 2) + if samples[1].Tokens != 0 || samples[1].Duration != 0 { + t.Fatal("counter/time regressions became activity", samples) + } + if AppendUsageSample(samples, UsageSample{}, 0) != nil { + t.Fatal("unbounded zero limit") + } +} + +func TestUsageDistributionPreservesSignedCorrectionsWithNonNegativeScale(t *testing.T) { + for _, tc := range []struct { + name string + total *float64 + rows []PlanUsageValue + peak float64 + }{ + {"negative only", new(-25.0), []PlanUsageValue{{Key: "cli", BasisPoints: -25}}, 0}, + {"negative without total", nil, []PlanUsageValue{{Key: "cli", BasisPoints: -25}}, 0}, + {"mixed corrections", new(-25.0), []PlanUsageValue{{Key: "cli", BasisPoints: 100}, {Key: "unknown", BasisPoints: -125}}, 1}, + {"positive total", new(100.0), []PlanUsageValue{{Key: "cli", BasisPoints: -25}}, 1}, + } { + t.Run(tc.name, func(t *testing.T) { + period := PlanUsagePeriod{UsedBasisPoints: tc.total, Breakdowns: []PlanUsageBreakdown{{Dimension: "surface", Rows: tc.rows}}} + d := WindowUsageDistribution(period, "surface") + if d.Peak != tc.peak || d.KnownEmpty || len(d.Rows) != len(tc.rows) { + t.Fatalf("invalid correction scale: %+v", d) + } + if tc.total == nil && d.Total != nil || tc.total != nil && (d.Total == nil || *d.Total != *tc.total/100) { + t.Fatalf("signed or unknown total changed: %+v", d) + } + for _, raw := range tc.rows { + found := false + for _, row := range d.Rows { + if row.Name == raw.Key && row.Value == raw.BasisPoints/100 { + found = true + } + } + if !found { + t.Fatalf("signed row changed: %+v in %+v", raw, d.Rows) + } + } + }) + } +} diff --git a/internal/codex/session_samples.go b/internal/codex/session_samples.go new file mode 100644 index 00000000..5d4a4785 --- /dev/null +++ b/internal/codex/session_samples.go @@ -0,0 +1,30 @@ +package codex + +import "time" + +// UsageSample is a process-local counter interval, never a billing record. +type UsageSample struct { + At time.Time `json:"at"` + Duration time.Duration `json:"-"` + Tokens int64 `json:"tokens"` +} + +// Callers own their measurement baseline; both interfaces use the same delta, +// elapsed-interval and bounded-history rules without mixing different zeroes. +func CaptureUsageSample(samples []UsageSample, current, previous int64, startedAt, at time.Time, limit int) []UsageSample { + if len(samples) > 0 { + startedAt = samples[len(samples)-1].At + } + return AppendUsageSample(samples, UsageSample{At: at, Duration: max(at.Sub(startedAt), 0), Tokens: max(current-previous, 0)}, limit) +} + +func AppendUsageSample(samples []UsageSample, sample UsageSample, limit int) []UsageSample { + if limit <= 0 { + return nil + } + if len(samples) >= limit { + copy(samples, samples[len(samples)-limit+1:]) + samples = samples[:limit-1] + } + return append(samples, sample) +} diff --git a/internal/codex/session_state.go b/internal/codex/session_state.go new file mode 100644 index 00000000..4d5d460d --- /dev/null +++ b/internal/codex/session_state.go @@ -0,0 +1,42 @@ +package codex + +// SessionAttentionLabel describes observed attention only. None is deliberately +// empty, not an inferred input request. Labels are canonical; adapters translate. +func SessionAttentionLabel(attention SessionAttention, context SessionContext) string { + switch attention { + case SessionAttentionInput: + return "INPUT NEEDED" + case SessionAttentionApproval: + return "APPROVAL NEEDED" + case SessionAttentionCheck: + return "CHECK SESSION" + case SessionAttentionError: + return SessionFailureLabel(context) + case SessionAttentionComplete: + return "TURN COMPLETE" + } + return "" +} + +// SessionStatus is the common observed-state projection. Staleness, selected +// reviews and scheduled triggers are adapter overlays, not backend attention. +func SessionStatus(active, working bool, attention SessionAttention, context SessionContext) string { + if label := SessionAttentionLabel(attention, context); label != "" { + return label + } + if working { + return "WORKING" + } + if !active { + return "INACTIVE" + } + return "IDLE" +} + +func SessionNeedsAttention(attention SessionAttention) bool { + return attention == SessionAttentionInput || attention == SessionAttentionApproval || attention == SessionAttentionCheck || attention == SessionAttentionError +} + +func SessionRecentlyActive(active bool, attention SessionAttention) bool { + return active && attention != SessionAttentionCheck && attention != SessionAttentionError +} diff --git a/internal/codex/types.go b/internal/codex/types.go index 69b76a6d..bb56f7cd 100644 --- a/internal/codex/types.go +++ b/internal/codex/types.go @@ -110,6 +110,28 @@ type Meter struct { Window Window Kind MeterKind Details string + Slot int // Backend slot: primary=0, secondary=1; never a display label. +} + +// MeterKey identifies a backend allowance independently of labels, consumption +// or reset timestamps. Features may add account/plan/cycle boundaries to it. +type MeterKey struct { + Limit string + Slot int + Kind MeterKind +} + +func (m Meter) Key() MeterKey { return MeterKey{Limit: m.LimitID, Slot: m.Slot, Kind: m.Kind} } +func (k MeterKey) String() string { return fmt.Sprintf("%s/%d/%d", k.Limit, k.Kind, k.Slot) } + +// DefinitionKey also distinguishes a changed allowance duration, but not the +// reset timestamp: a scheduled quota-recovery follow-up must survive a reset. +func (m Meter) DefinitionKey() string { + duration := int64(-1) + if m.Window.WindowDurationMins != nil { + duration = *m.Window.WindowDurationMins + } + return fmt.Sprintf("%s/%d", m.Key(), duration) } type MeterKind int @@ -151,7 +173,7 @@ func (s Snapshot) Meters() []Meter { meters = append(meters, Meter{Bucket: name, LimitID: key, Name: windowName(*bucket.Primary), Window: *bucket.Primary}) } if bucket.Secondary != nil { - meters = append(meters, Meter{Bucket: name, LimitID: key, Name: windowName(*bucket.Secondary), Window: *bucket.Secondary}) + meters = append(meters, Meter{Bucket: name, LimitID: key, Name: windowName(*bucket.Secondary), Window: *bucket.Secondary, Slot: 1}) } if bucket.IndividualLimit != nil { limit := bucket.IndividualLimit diff --git a/internal/codex/usage_categories.go b/internal/codex/usage_categories.go index 2e5c7a29..a56b0b0f 100644 --- a/internal/codex/usage_categories.go +++ b/internal/codex/usage_categories.go @@ -3,8 +3,114 @@ package codex import "sort" type UsageCategory struct { - Name string - Value float64 + Name string `json:"name"` + Value float64 `json:"value"` +} + +func UsageDimensions() []string { return []string{"surface", "model", "feature", "task start"} } + +// UsageWarningLevel classifies a historical quota percentage, not relative +// daily usage units. Renderers choose colours, never their own thresholds. +func UsageWarningLevel(known bool, percent float64) string { + switch { + case !known: + return "unknown" + case percent >= 100: + return "full" + case percent >= 80: + return "near" + default: + return "normal" + } +} + +// UsageDistribution owns numeric scaling and ordering, never terminal/SVG +// geometry. A fractional total remains fractional; zero alone needs a fallback. +type UsageDistribution struct { + Rows []UsageCategory `json:"rows"` + Total *float64 `json:"total"` + Peak float64 `json:"peak"` + KnownEmpty bool `json:"knownEmpty"` + Level string `json:"level,omitempty"` +} + +func usageDistribution(groups map[string]map[string]float64, dimension string, total *float64) UsageDistribution { + d := UsageDistribution{Rows: UsageCategories(groups, dimension), Total: total} + if total != nil { + d.Peak = max(0, *total) + d.KnownEmpty = *total == 0 && groups[dimension] != nil + } + for _, row := range d.Rows { + d.Peak = max(d.Peak, row.Value) + } + return d +} + +func DailyUsageDistribution(day UsageBreakdownDay, dimension string) UsageDistribution { + return usageDistribution(day.Groups, dimension, &day.Total) +} + +func WindowUsageDistribution(period PlanUsagePeriod, dimension string) UsageDistribution { + wire := dimension + switch dimension { + case "feature": + wire = "thread_source" + case "task start": + wire = "turn_trigger" + } + groups := map[string]map[string]float64{} + for _, breakdown := range period.Breakdowns { + if breakdown.Dimension != wire { + continue + } + if groups[dimension] == nil { + groups[dimension] = map[string]float64{} + } + for _, row := range breakdown.Rows { + groups[dimension][row.Key] += row.BasisPoints / 100 + } + } + var total *float64 + value := 0.0 + if period.UsedBasisPoints != nil { + value = *period.UsedBasisPoints / 100 + total = &value + } + d := usageDistribution(groups, dimension, total) + d.Level = UsageWarningLevel(total != nil, value) + return d +} + +// ProjectUsageReports copies the report containers and adds display-only +// distributions. The raw backend observation is never mutated or persisted. +func ProjectUsageReports(reports *UsageReports) *UsageReports { + if reports == nil { + return nil + } + r := *reports + if r.Daily != nil { + daily := *r.Daily + daily.Days = append([]UsageBreakdownDay(nil), daily.Days...) + for i := range daily.Days { + daily.Days[i].Views = map[string]UsageDistribution{} + for _, dimension := range UsageDimensions() { + daily.Days[i].Views[dimension] = DailyUsageDistribution(daily.Days[i], dimension) + } + } + r.Daily = &daily + } + if r.Plan != nil { + plan := *r.Plan + plan.Periods = append([]PlanUsagePeriod(nil), plan.Periods...) + for i := range plan.Periods { + plan.Periods[i].Views = map[string]UsageDistribution{} + for _, dimension := range UsageDimensions() { + plan.Periods[i].Views[dimension] = WindowUsageDistribution(plan.Periods[i], dimension) + } + } + r.Plan = &plan + } + return &r } // UsageCategories returns a deterministic ordering for a reported dimension. diff --git a/internal/codex/usage_reports.go b/internal/codex/usage_reports.go index b7b90fde..b398c5fe 100644 --- a/internal/codex/usage_reports.go +++ b/internal/codex/usage_reports.go @@ -33,8 +33,9 @@ type DailyUsageReport struct { } type UsageBreakdownDay struct { - Date string `json:"date"` - Total float64 `json:"total"` + Views map[string]UsageDistribution `json:"views,omitempty"` + Date string `json:"date"` + Total float64 `json:"total"` // Each dimension is a different breakdown of the SAME total, not additive. Groups map[string]map[string]float64 `json:"groups"` } @@ -50,14 +51,15 @@ type PlanUsageReport struct { } type PlanUsagePeriod struct { - ID string `json:"id"` - WindowMinutes uint32 `json:"window_minutes"` - PlanType string `json:"plan_type"` - StartsAt string `json:"starts_at"` - EndsAt string `json:"ends_at"` - AccountingComplete bool `json:"accounting_complete"` - UsedBasisPoints *float64 `json:"used_basis_points"` - Breakdowns []PlanUsageBreakdown `json:"breakdowns"` + Views map[string]UsageDistribution `json:"views,omitempty"` + ID string `json:"id"` + WindowMinutes uint32 `json:"window_minutes"` + PlanType string `json:"plan_type"` + StartsAt string `json:"starts_at"` + EndsAt string `json:"ends_at"` + AccountingComplete bool `json:"accounting_complete"` + UsedBasisPoints *float64 `json:"used_basis_points"` + Breakdowns []PlanUsageBreakdown `json:"breakdowns"` } type PlanUsageBreakdown struct { @@ -286,7 +288,7 @@ func normalizeDailyReport(wire dailyUsageWire, from, through string, now time.Ti d.Groups[dimension][reportKey(key)] += value } if complete { - for _, dimension := range []string{"model", "surface", "feature", "task start"} { + for _, dimension := range UsageDimensions() { d.Groups[dimension] = map[string]float64{} } for _, row := range day.Attribution { diff --git a/internal/quotaestimate/estimate.go b/internal/quotaestimate/estimate.go new file mode 100644 index 00000000..57e11949 --- /dev/null +++ b/internal/quotaestimate/estimate.go @@ -0,0 +1,389 @@ +// Package quotaestimate owns process-local API-equivalent learning, independent of presentation. +package quotaestimate + +import ( + "fmt" + "math" + "sort" + "strings" + "time" + + "github.com/merefield/codexometer/internal/codex" +) + +const ( + MinimumDelta = 5 + MaxSamples = 12 + MaxAge = 45 * 24 * time.Hour +) + +// State is in memory only and must never be serialized to a browser or disk. +type State struct { + Anchors map[string]Anchor + Evidence []Sample + Account, CoverageIssue string +} + +func AccountingEqual(left, right codex.LiveUsageSnapshot) bool { + return left.APIEqCoverageIssue == right.APIEqCoverageIssue && + left.APIEqUSD == right.APIEqUSD && + left.APIEqTierPremiumUSD == right.APIEqTierPremiumUSD && + left.APIEqUnknownTierCalls == right.APIEqUnknownTierCalls && + left.APIEqPricedCalls == right.APIEqPricedCalls && + left.APIEqUnpricedCalls == right.APIEqUnpricedCalls && + left.APIEqMissingUsageCalls == right.APIEqMissingUsageCalls && + left.APIEqInconsistentUsageCalls == right.APIEqInconsistentUsageCalls && + left.APIEqAccountingAdjustments == right.APIEqAccountingAdjustments && + left.APIEqPendingCalls == right.APIEqPendingCalls +} + +// Sample is process-local and content-free. Its key contains only an +// in-memory account fingerprint plus the quota-window identity; it retains no +// email, session, model-call, or token-event identifier and is never persisted. +type Sample struct { + Key string `json:"key"` + CapacityUSD float64 `json:"capacityUsd"` + LowUSD float64 `json:"lowUsd"` + HighUSD float64 `json:"highUsd"` + DeltaPercent int `json:"deltaPercent"` + ObservedAtUnix int64 `json:"observedAt"` + PricingRetrievedOn string `json:"pricingRetrievedOn"` + PremiumCapacityUSD float64 `json:"premiumCapacityUsd"` + UnknownTier bool `json:"unknownTier"` +} + +type Anchor struct { + UsedPercent int + ResetAt int64 + CostUSD float64 + PremiumUSD float64 + UnknownTierCalls int64 + PricedCalls int64 + UnpricedCalls int64 + MissingUsageCalls int64 + InconsistentUsageCalls int64 + AccountingAdjustments int64 + RestartReason string + RestartIssue codex.APIEqDiagnostic +} + +const ( + RestartAccountChanged = "ACCOUNT CHANGED" + RestartAccountingRebased = "LOCAL ACCOUNTING REBASED" + RestartCoverageGap = "LOCAL COVERAGE GAP" + RestartUnpricedUsage = "UNPRICED USAGE" + RestartMissingUsage = "MISSING RESPONSE USAGE" + RestartInconsistentUsage = "INCONSISTENT RESPONSE USAGE" + RestartWindowChanged = "WINDOW DEFINITION CHANGED" + RestartWindowReset = "WINDOW RESET" +) + +type Estimate struct { + CurrentLow float64 + CurrentHigh float64 + FullLow float64 + FullHigh float64 + Samples int + Confidence string + StandardFull float64 + HasPremium bool + UnknownTier bool +} + +// A known coverage gap invalidates evidence even when another error or an +// active benchmark prevents using the associated quota observation. +func (m *State) ObserveCoverage(issue string) { + previous := m.CoverageIssue + m.CoverageIssue = issue + if previous != "" || issue != "" { + m.Anchors = make(map[string]Anchor) + m.Evidence = nil + } +} + +func (m *State) Observe(snapshot codex.Snapshot, usage codex.LiveUsageSnapshot, at time.Time) { + if at.IsZero() { + at = time.Now() + } + account := strings.TrimSpace(snapshot.AccountFingerprint) + if account == "" { + return + } + accountChanged := m.Account != "" && m.Account != account + if accountChanged { + m.Anchors = make(map[string]Anchor) + m.Evidence = nil + } + m.Account = account + m.ObserveCoverage(usage.APIEqCoverageIssue) + if usage.APIEqCoverageIssue != "" { + return + } + + if m.Anchors == nil { + m.Anchors = make(map[string]Anchor) + } + priorAnchorKeys := make(map[string]struct{}, len(m.Anchors)) + for key := range m.Anchors { + priorAnchorKeys[key] = struct{}{} + } + m.Evidence = ValidSamples(m.Evidence, at) + for _, meter := range snapshot.Meters() { + if !Eligible(snapshot, meter) { + continue + } + key := Key(snapshot, meter) + current := Anchor{ + UsedPercent: min(max(meter.Window.UsedPercent, 0), 100), + ResetAt: optionalUnix(meter.Window.ResetsAt), CostUSD: usage.APIEqUSD + usage.APIEqTierPremiumUSD, + PremiumUSD: usage.APIEqTierPremiumUSD, UnknownTierCalls: usage.APIEqUnknownTierCalls, + PricedCalls: usage.APIEqPricedCalls, UnpricedCalls: usage.APIEqUnpricedCalls, + MissingUsageCalls: usage.APIEqMissingUsageCalls, + InconsistentUsageCalls: usage.APIEqInconsistentUsageCalls, + AccountingAdjustments: usage.APIEqAccountingAdjustments, + } + anchor, exists := m.Anchors[key] + if !exists { + switch { + case accountChanged: + current.RestartReason = RestartAccountChanged + case hasRelatedAnchor(priorAnchorKeys, snapshot, meter): + current.RestartReason = RestartWindowChanged + } + m.Anchors[key] = current + continue + } + if windowReset(anchor, current, at) { + current.RestartReason = RestartWindowReset + m.Anchors[key] = current + continue + } + if current.CostUSD < anchor.CostUSD || current.PricedCalls < anchor.PricedCalls || + current.UnpricedCalls < anchor.UnpricedCalls || + current.UnknownTierCalls < anchor.UnknownTierCalls || + current.MissingUsageCalls < anchor.MissingUsageCalls || + current.InconsistentUsageCalls < anchor.InconsistentUsageCalls || + current.AccountingAdjustments < anchor.AccountingAdjustments { + current.RestartReason = RestartAccountingRebased + m.Anchors[key] = current + continue + } + // The backend may revise an upcoming rolling-window reset timestamp + // without starting a new quota window. Preserve the earliest known + // boundary so a later observation can still detect that it passed, + // while retaining the clean movement and accounting baselines now. + if current.ResetAt > 0 && (anchor.ResetAt == 0 || current.ResetAt < anchor.ResetAt) { + anchor.ResetAt = current.ResetAt + m.Anchors[key] = anchor + } + if current.UnpricedCalls > anchor.UnpricedCalls { + current.RestartReason = RestartUnpricedUsage + current.RestartIssue = latestIntervalDiagnostic(usage, codex.UsageIssueUnpriced, anchor.UnpricedCalls) + m.Anchors[key] = current + continue + } + switch { + case current.MissingUsageCalls > anchor.MissingUsageCalls: + current.RestartReason = RestartMissingUsage + current.RestartIssue = latestIntervalDiagnostic(usage, codex.UsageIssueMissing, anchor.MissingUsageCalls) + case current.InconsistentUsageCalls > anchor.InconsistentUsageCalls: + current.RestartReason = RestartInconsistentUsage + current.RestartIssue = latestIntervalDiagnostic(usage, codex.UsageIssueInconsistent, anchor.InconsistentUsageCalls) + case current.AccountingAdjustments > anchor.AccountingAdjustments: + current.RestartReason = RestartAccountingRebased + } + if current.RestartReason != "" { + m.Anchors[key] = current + continue + } + deltaPercent := current.UsedPercent - anchor.UsedPercent + if deltaPercent < MinimumDelta { + continue + } + deltaCost := current.CostUSD - anchor.CostUSD + if deltaCost <= 0 || current.PricedCalls == anchor.PricedCalls { + current.RestartReason = RestartCoverageGap + m.Anchors[key] = current + continue + } + capacity := deltaCost * 100 / float64(deltaPercent) + low := deltaCost * 100 / float64(deltaPercent+1) + high := deltaCost * 100 / float64(deltaPercent-1) + m.Evidence = append(m.Evidence, Sample{ + Key: key, CapacityUSD: capacity, LowUSD: low, HighUSD: high, + DeltaPercent: deltaPercent, ObservedAtUnix: at.Unix(), + PricingRetrievedOn: codex.StandardAPIPricingRetrievedOn, + PremiumCapacityUSD: (current.PremiumUSD - anchor.PremiumUSD) * 100 / float64(deltaPercent), + UnknownTier: current.UnknownTierCalls > anchor.UnknownTierCalls, + }) + m.Evidence = trimSamples(m.Evidence, key) + m.Anchors[key] = current + } +} + +func hasRelatedAnchor(priorKeys map[string]struct{}, snapshot codex.Snapshot, meter codex.Meter) bool { + wantedAccount := strings.TrimSpace(snapshot.AccountFingerprint) + wantedDefinition := meter.Key().String() + "/" + for key := range priorKeys { + parts := strings.SplitN(key, "|", 3) + if len(parts) == 3 && parts[0] == wantedAccount && strings.HasPrefix(parts[2], wantedDefinition) { + return true + } + } + return false +} + +func windowReset(anchor, current Anchor, observedAt time.Time) bool { + if current.UsedPercent < anchor.UsedPercent { + return true + } + if anchor.ResetAt <= 0 || current.ResetAt <= anchor.ResetAt { + return false + } + return !observedAt.Before(time.Unix(anchor.ResetAt, 0)) +} + +func Eligible(snapshot codex.Snapshot, meter codex.Meter) bool { + return meter.Kind == codex.MeterQuotaWindow && snapshot.AccountFingerprint != "" && + strings.EqualFold(strings.TrimSpace(meter.LimitID), "codex") +} + +func optionalUnix(value *int64) int64 { + if value == nil { + return 0 + } + return *value +} + +func Key(snapshot codex.Snapshot, meter codex.Meter) string { + account := strings.TrimSpace(snapshot.AccountFingerprint) + plan := "unknown" + if len(snapshot.RateLimitsByLimitID) > 0 { + if limit, ok := snapshot.RateLimitsByLimitID[meter.LimitID]; ok && limit.PlanType != nil { + plan = strings.ToLower(strings.TrimSpace(*limit.PlanType)) + } + } else if snapshot.RateLimits.PlanType != nil { + plan = strings.ToLower(strings.TrimSpace(*snapshot.RateLimits.PlanType)) + } + return fmt.Sprintf("%s|%s|%s", account, plan, meter.DefinitionKey()) +} + +func ValidSamples(samples []Sample, now time.Time) []Sample { + valid := make([]Sample, 0, len(samples)) + cutoff := now.Add(-MaxAge).Unix() + for _, sample := range samples { + if sample.Key == "" || sample.PricingRetrievedOn != codex.StandardAPIPricingRetrievedOn || + sample.ObservedAtUnix < cutoff || sample.ObservedAtUnix > now.Add(time.Hour).Unix() || + sample.DeltaPercent < MinimumDelta || !positiveFinite(sample.CapacityUSD) || + !positiveFinite(sample.LowUSD) || !positiveFinite(sample.HighUSD) || + math.IsNaN(sample.PremiumCapacityUSD) || math.IsInf(sample.PremiumCapacityUSD, 0) || + sample.PremiumCapacityUSD >= sample.CapacityUSD || + sample.LowUSD > sample.CapacityUSD || sample.CapacityUSD > sample.HighUSD { + continue + } + valid = append(valid, sample) + } + return valid +} + +func positiveFinite(value float64) bool { + return value > 0 && !math.IsNaN(value) && !math.IsInf(value, 0) +} + +func trimSamples(samples []Sample, key string) []Sample { + matching := 0 + for _, sample := range samples { + if sample.Key == key { + matching++ + } + } + remove := matching - MaxSamples + if remove <= 0 { + return samples + } + trimmed := make([]Sample, 0, len(samples)-remove) + for _, sample := range samples { + if sample.Key == key && remove > 0 { + remove-- + continue + } + trimmed = append(trimmed, sample) + } + return trimmed +} + +func (m State) Estimate(snapshot codex.Snapshot, meter codex.Meter, now time.Time) (Estimate, bool) { + if m.CoverageIssue != "" { + return Estimate{}, false + } + key := Key(snapshot, meter) + matching := make([]Sample, 0, MaxSamples) + for _, sample := range ValidSamples(m.Evidence, now) { + if sample.Key == key { + matching = append(matching, sample) + } + } + if len(matching) == 0 { + return Estimate{}, false + } + lows := make([]float64, 0, len(matching)) + highs := make([]float64, 0, len(matching)) + centers := make([]float64, 0, len(matching)) + standardCenters := make([]float64, 0, len(matching)) + hasPremium, unknownTier := false, false + totalDelta := 0 + for _, sample := range matching { + lows = append(lows, sample.LowUSD) + highs = append(highs, sample.HighUSD) + centers = append(centers, sample.CapacityUSD) + standardCenters = append(standardCenters, sample.CapacityUSD-sample.PremiumCapacityUSD) + hasPremium = hasPremium || sample.PremiumCapacityUSD != 0 + unknownTier = unknownTier || sample.UnknownTier + totalDelta += sample.DeltaPercent + } + fullLow, fullHigh := medianFloat(lows), medianFloat(highs) + center := medianFloat(centers) + minCenter, maxCenter := slicesMinMax(centers) + // Keep this identifier canonical: compact presentations use L/M codes. + confidence := "LOW" + if !unknownTier && len(matching) >= 3 && totalDelta >= 15 && center > 0 && + (fullHigh-fullLow)/center <= 0.45 && (maxCenter-minCenter)/center <= 0.50 { + // Local rollout coverage cannot prove that another machine or client did + // not consume quota, so the estimator intentionally caps at MEDIUM. + confidence = "MED" + } + used := float64(min(max(meter.Window.UsedPercent, 0), 100)) / 100 + return Estimate{ + CurrentLow: fullLow * used, CurrentHigh: fullHigh * used, + FullLow: fullLow, FullHigh: fullHigh, Samples: len(matching), Confidence: confidence, + StandardFull: medianFloat(standardCenters), HasPremium: hasPremium, UnknownTier: unknownTier, + }, true +} + +func slicesMinMax(values []float64) (float64, float64) { + minimum, maximum := values[0], values[0] + for _, value := range values[1:] { + minimum = min(minimum, value) + maximum = max(maximum, value) + } + return minimum, maximum +} + +func medianFloat(values []float64) float64 { + sort.Float64s(values) + middle := len(values) / 2 + if len(values)%2 == 1 { + return values[middle] + } + return (values[middle-1] + values[middle]) / 2 +} + +func latestIntervalDiagnostic(usage codex.LiveUsageSnapshot, kind codex.APIEqIssueKind, after int64) codex.APIEqDiagnostic { + for i := len(usage.APIEqDiagnostics) - 1; i >= 0; i-- { + issue := usage.APIEqDiagnostics[i] + if issue.Kind == kind && issue.Counter > after { + return issue + } + } + return codex.APIEqDiagnostic{} +} diff --git a/internal/quotaestimate/estimate_test.go b/internal/quotaestimate/estimate_test.go new file mode 100644 index 00000000..bf982346 --- /dev/null +++ b/internal/quotaestimate/estimate_test.go @@ -0,0 +1,128 @@ +package quotaestimate + +import ( + "math" + "testing" + "time" + + "github.com/merefield/codexometer/internal/codex" +) + +func TestIndependentEngineSeparatesSlotsAndPreservesRevisedResetProgress(t *testing.T) { + now := time.Unix(1800000000, 0) + duration := int64(300) + reset := now.Add(time.Hour).Unix() + s := codex.Snapshot{AccountFingerprint: "synthetic", RateLimits: codex.RateLimitSnapshot{Primary: &codex.Window{UsedPercent: 10, WindowDurationMins: &duration, ResetsAt: &reset}, Secondary: &codex.Window{UsedPercent: 30, WindowDurationMins: &duration, ResetsAt: &reset}}} + var state State + state.Observe(s, codex.LiveUsageSnapshot{}, now) + if len(state.Anchors) != 2 { + t.Fatal("same duration slots shared an anchor", state.Anchors) + } + s.RateLimits.Primary.UsedPercent = 12 + reset++ + state.Observe(s, codex.LiveUsageSnapshot{APIEqUSD: 1, APIEqPricedCalls: 1}, now.Add(time.Minute)) + a := state.Anchors[Key(s, s.Meters()[0])] + if a.UsedPercent != 10 || a.RestartReason != "" { + t.Fatal("reset revision discarded clean movement", a) + } + s.RateLimits.Primary.UsedPercent = 15 + state.Observe(s, codex.LiveUsageSnapshot{APIEqUSD: 1, APIEqPricedCalls: 1}, now.Add(2*time.Minute)) + if estimate, ok := state.Estimate(s, s.Meters()[0], now.Add(2*time.Minute)); !ok || estimate.Samples != 1 || estimate.FullLow != 100.0/6 { + t.Fatal("independent estimate", estimate, ok) + } + state.ObserveCoverage("missing local configuration") + if len(state.Anchors) != 0 || len(state.Evidence) != 0 { + t.Fatal("coverage gap retained evidence") + } +} + +func TestEngineRestartsTaintedIntervals(t *testing.T) { + now := time.Unix(1800000000, 0) + duration, reset := int64(300), now.Add(time.Hour).Unix() + initial := codex.LiveUsageSnapshot{APIEqUSD: 1, APIEqPricedCalls: 1} + for _, tc := range []struct { + name, reason string + change func(*codex.Snapshot, *codex.LiveUsageSnapshot) + }{ + {"account", RestartAccountChanged, func(s *codex.Snapshot, _ *codex.LiveUsageSnapshot) { s.AccountFingerprint = "other" }}, + {"plan", RestartWindowChanged, func(s *codex.Snapshot, _ *codex.LiveUsageSnapshot) { p := "pro"; s.RateLimits.PlanType = &p }}, + {"duration", RestartWindowChanged, func(s *codex.Snapshot, _ *codex.LiveUsageSnapshot) { + d := int64(600) + s.RateLimits.Primary.WindowDurationMins = &d + }}, + {"reset", RestartWindowReset, func(s *codex.Snapshot, _ *codex.LiveUsageSnapshot) { s.RateLimits.Primary.UsedPercent = 0 }}, + {"rebased", RestartAccountingRebased, func(_ *codex.Snapshot, u *codex.LiveUsageSnapshot) { u.APIEqUSD = 0 }}, + {"adjustment", RestartAccountingRebased, func(_ *codex.Snapshot, u *codex.LiveUsageSnapshot) { u.APIEqAccountingAdjustments++ }}, + {"unpriced", RestartUnpricedUsage, func(_ *codex.Snapshot, u *codex.LiveUsageSnapshot) { u.APIEqUnpricedCalls++ }}, + {"missing", RestartMissingUsage, func(_ *codex.Snapshot, u *codex.LiveUsageSnapshot) { u.APIEqMissingUsageCalls++ }}, + {"inconsistent", RestartInconsistentUsage, func(_ *codex.Snapshot, u *codex.LiveUsageSnapshot) { u.APIEqInconsistentUsageCalls++ }}, + {"no cost movement", RestartCoverageGap, func(_ *codex.Snapshot, u *codex.LiveUsageSnapshot) { *u = initial }}, + } { + t.Run(tc.name, func(t *testing.T) { + snapshot := codex.Snapshot{AccountFingerprint: "synthetic", RateLimits: codex.RateLimitSnapshot{Primary: &codex.Window{UsedPercent: 10, WindowDurationMins: &duration, ResetsAt: &reset}}} + var state State + state.Observe(snapshot, initial, now) + snapshot.RateLimits.Primary.UsedPercent = 15 + usage := codex.LiveUsageSnapshot{APIEqUSD: 2, APIEqPricedCalls: 2} + tc.change(&snapshot, &usage) + state.Observe(snapshot, usage, now.Add(time.Minute)) + if a := state.Anchors[Key(snapshot, snapshot.Meters()[0])]; a.RestartReason != tc.reason || len(state.Evidence) != 0 { + t.Fatalf("tainted interval: anchor=%+v evidence=%+v", a, state.Evidence) + } + }) + } +} + +func TestEngineValidatesEvidenceAndBoundsConfidence(t *testing.T) { + now := time.Unix(1800000000, 0) + snapshot := codex.Snapshot{AccountFingerprint: "synthetic", RateLimits: codex.RateLimitSnapshot{Primary: &codex.Window{UsedPercent: 25}}} + meter := snapshot.Meters()[0] + sample := Sample{Key: Key(snapshot, meter), CapacityUSD: 100, LowUSD: 90, HighUSD: 110, DeltaPercent: 10, ObservedAtUnix: now.Unix(), PricingRetrievedOn: codex.StandardAPIPricingRetrievedOn} + for _, tc := range []struct { + name string + change func(*Sample) + }{ + {"old", func(s *Sample) { s.ObservedAtUnix = now.Add(-MaxAge - time.Second).Unix() }}, + {"future", func(s *Sample) { s.ObservedAtUnix = now.Add(time.Hour + time.Second).Unix() }}, + {"pricing", func(s *Sample) { s.PricingRetrievedOn = "old" }}, + {"nan", func(s *Sample) { s.CapacityUSD = math.NaN() }}, + {"rounding", func(s *Sample) { s.DeltaPercent = MinimumDelta - 1 }}, + {"premium", func(s *Sample) { s.PremiumCapacityUSD = s.CapacityUSD }}, + {"bounds", func(s *Sample) { s.LowUSD = s.CapacityUSD + 1 }}, + } { + t.Run(tc.name, func(t *testing.T) { + invalid := sample + tc.change(&invalid) + if got := ValidSamples([]Sample{invalid, sample}, now); len(got) != 1 || got[0] != sample { + t.Fatalf("invalid evidence retained: %+v", got) + } + }) + } + state := State{Evidence: []Sample{sample, sample, sample}} + if estimate, ok := state.Estimate(snapshot, meter, now); !ok || estimate.Confidence != "MED" || estimate.CurrentLow != 22.5 || estimate.CurrentHigh != 27.5 { + t.Fatalf("medium confidence estimate: %+v %v", estimate, ok) + } + state.Evidence[0].UnknownTier = true + state.Evidence[0].PremiumCapacityUSD = 20 + if estimate, ok := state.Estimate(snapshot, meter, now); !ok || estimate.Confidence != "LOW" || !estimate.UnknownTier || !estimate.HasPremium { + t.Fatalf("unknown tier must cap confidence: %+v %v", estimate, ok) + } + state.CoverageIssue = "gap" + if _, ok := state.Estimate(snapshot, meter, now); ok { + t.Fatal("coverage gap must suppress estimate") + } + state.CoverageIssue = "" + state.Evidence = nil + if _, ok := state.Estimate(snapshot, meter, now); ok { + t.Fatal("empty evidence must suppress estimate") + } + var bounded []Sample + for i := range MaxSamples + 3 { + s := sample + s.ObservedAtUnix += int64(i) + bounded = trimSamples(append(bounded, s), sample.Key) + } + if len(bounded) != MaxSamples || bounded[0].ObservedAtUnix != sample.ObservedAtUnix+3 { + t.Fatal("old evidence not trimmed", bounded) + } +} diff --git a/internal/quotagraph/graph.go b/internal/quotagraph/graph.go index 991f471d..3cfd2e35 100644 --- a/internal/quotagraph/graph.go +++ b/internal/quotagraph/graph.go @@ -19,10 +19,20 @@ type Point struct { } func Elapsed(window codex.Window, now time.Time) (float64, bool) { + fraction, known := ElapsedFraction(window, now) + return 100 * fraction, known +} + +// ElapsedFraction is shared by quota health, reset gauges and graph geometry. +// Subtract timestamps before converting to floating point to retain subsecond +// precision without cancellation against a large Unix timestamp. +func ElapsedFraction(window codex.Window, now time.Time) (float64, bool) { if window.WindowDurationMins == nil || *window.WindowDurationMins <= 0 || window.ResetsAt == nil || *window.ResetsAt <= 0 { return 0, false } - return max(0, min(100, 100*(1-(float64(*window.ResetsAt)-float64(now.UnixMilli())/1000)/(float64(*window.WindowDurationMins)*60)))), true + duration := float64(*window.WindowDurationMins) * 60 + elapsed := duration - time.Unix(*window.ResetsAt, 0).Sub(now).Seconds() + return max(0, min(1, elapsed/duration)), true } func Record(window, previous codex.Window, points []Point, now time.Time, gap bool) []Point { @@ -48,31 +58,14 @@ func Record(window, previous codex.Window, points []Point, now time.Time, gap bo return points } -type Key struct { - Limit string - Slot int - Kind codex.MeterKind -} +type Key = codex.MeterKey // Keys follows Meters' ordering without identifying windows by mutable labels. func Keys(snapshot codex.Snapshot) []Key { meters := snapshot.Meters() keys := make([]Key, len(meters)) - slots := map[string]int{} for i, meter := range meters { - key := Key{Limit: meter.LimitID, Kind: meter.Kind} - if meter.Kind == codex.MeterQuotaWindow { - bucket := snapshot.RateLimits - if len(snapshot.RateLimitsByLimitID) > 0 { - bucket = snapshot.RateLimitsByLimitID[meter.LimitID] - } - key.Slot = slots[meter.LimitID] - if bucket.Primary == nil { - key.Slot++ - } - slots[meter.LimitID]++ - } - keys[i] = key + keys[i] = meter.Key() } return keys } @@ -96,10 +89,11 @@ func (h *History) Observe(snapshot codex.Snapshot, failed bool, at time.Time) { h.Series = nil } h.Account = snapshot.AccountFingerprint - keys := Keys(snapshot) - next := make(map[Key]Series, len(keys)) - for i, meter := range snapshot.Meters() { - old := h.Series[keys[i]] + meters := snapshot.Meters() + next := make(map[Key]Series, len(meters)) + for _, meter := range meters { + key := meter.Key() + old := h.Series[key] window := meter.Window // Retain values, not pointers into a caller's mutable snapshot. if window.WindowDurationMins != nil { @@ -110,7 +104,7 @@ func (h *History) Observe(snapshot codex.Snapshot, failed bool, at time.Time) { reset := *window.ResetsAt window.ResetsAt = &reset } - next[keys[i]] = Series{Window: window, Points: Record(window, old.Window, old.Points, at, h.gap)} + next[key] = Series{Window: window, Points: Record(window, old.Window, old.Points, at, h.gap)} } h.Series, h.gap = next, false } @@ -228,7 +222,12 @@ func Project(mode Mode, window codex.Window, points []Point, now time.Time) (Tre return Trend{Slope: slope, Intercept: intercept, Projected: used + slope*(100-elapsed), Start: start}, true } -type Segment struct{ X1, Y1, X2, Y2 float64 } +type Segment struct { + X1 float64 `json:"x1"` + Y1 float64 `json:"y1"` + X2 float64 `json:"x2"` + Y2 float64 `json:"y2"` +} func (t Trend) Segment() (Segment, bool) { slope, lower, upper := t.Slope, 0.0, 100.0 @@ -242,3 +241,35 @@ func (t Trend) Segment() (Segment, bool) { valid := x1 <= x2 && y1 >= lower-1e-9 && y1 <= upper+1e-9 && y2 >= lower-1e-9 && y2 <= upper+1e-9 return Segment{X1: x1, Y1: max(lower, min(upper, y1)), X2: x2, Y2: max(lower, min(upper, y2))}, valid } + +// Plot is a numeric snapshot for either renderer. In particular a browser must +// not implement its own regression, coverage rules or clipping algorithm. +type Plot struct { + Elapsed float64 `json:"elapsed"` + Known bool `json:"known"` + Modes []Projection `json:"modes" ts:"nonnull"` +} + +type Projection struct { + ID string `json:"id"` + Available bool `json:"available"` + Projected float64 `json:"projected"` + Safe bool `json:"safe"` + Segment *Segment `json:"segment,omitempty"` +} + +func ProjectPlot(window codex.Window, points []Point, now time.Time) Plot { + elapsed, known := Elapsed(window, now) + plot := Plot{Elapsed: elapsed, Known: known, Modes: make([]Projection, 0, ModeCount)} + for mode := WindowStart; mode < ModeCount; mode++ { + p := Projection{ID: [...]string{"window", "halfHour", "hour", "day", "off"}[mode], Available: Available(mode, window, points, now)} + if trend, ok := Project(mode, window, points, now); ok { + p.Projected, p.Safe = trend.Projected, trend.Projected <= 100 + if segment, visible := trend.Segment(); visible { + p.Segment = &segment + } + } + plot.Modes = append(plot.Modes, p) + } + return plot +} diff --git a/internal/quotagraph/graph_test.go b/internal/quotagraph/graph_test.go index ea0c00bb..2bf1cf20 100644 --- a/internal/quotagraph/graph_test.go +++ b/internal/quotagraph/graph_test.go @@ -13,6 +13,42 @@ func testWindow(now time.Time, used int) codex.Window { return codex.Window{UsedPercent: used, WindowDurationMins: &duration, ResetsAt: &reset} } +func TestElapsedFractionBoundaries(t *testing.T) { + reset := time.Unix(1800000000, 0) + w := codex.Window{WindowDurationMins: new(int64(300)), ResetsAt: new(reset.Unix())} + for _, tc := range []struct { + name string + now time.Time + want float64 + }{ + {"before start", reset.Add(-6 * time.Hour), 0}, + {"at start", reset.Add(-5 * time.Hour), 0}, + {"on twenty-percent pace", reset.Add(-4 * time.Hour), .2}, + {"halfway", reset.Add(-150 * time.Minute), .5}, + {"subsecond", reset.Add(-5*time.Hour + 500*time.Millisecond), .5 / 18000}, + {"at reset", reset, 1}, + {"after reset", reset.Add(time.Hour), 1}, + } { + t.Run(tc.name, func(t *testing.T) { + fraction, known := ElapsedFraction(w, tc.now) + percent, percentKnown := Elapsed(w, tc.now) + if !known || !percentKnown || math.Abs(fraction-tc.want) > 1e-15 || percent != 100*fraction { + t.Fatalf("fraction=%v percent=%v known=%v/%v want=%v", fraction, percent, known, percentKnown, tc.want) + } + }) + } + for _, invalid := range []codex.Window{ + {}, {WindowDurationMins: new(int64(0)), ResetsAt: w.ResetsAt}, + {WindowDurationMins: new(int64(-1)), ResetsAt: w.ResetsAt}, + {WindowDurationMins: w.WindowDurationMins}, + {WindowDurationMins: w.WindowDurationMins, ResetsAt: new(int64(0))}, + } { + if value, known := ElapsedFraction(invalid, reset); known || value != 0 { + t.Fatalf("invalid reset metadata yielded progress: %v %v", value, known) + } + } +} + func TestWindowStartUsesSnapshotWithoutHistory(t *testing.T) { now := time.Unix(1800000000, 0) w := testWindow(now, 25) diff --git a/internal/schedule/identity_test.go b/internal/schedule/identity_test.go new file mode 100644 index 00000000..2ce8f1c0 --- /dev/null +++ b/internal/schedule/identity_test.go @@ -0,0 +1,28 @@ +package schedule + +import ( + "github.com/merefield/codexometer/internal/codex" + "testing" +) + +func TestCoverageCannotHideMissingSameNamedWindow(t *testing.T) { + minutes := int64(300) + w := codex.Window{WindowDurationMins: &minutes} + s := codex.Snapshot{RateLimits: codex.RateLimitSnapshot{Primary: &w, Secondary: &w}} + j := Job{Windows: WindowKeys(s)} + if !Covers(j, s) { + t.Fatal("unchanged inventory not covered") + } + s.RateLimits.Primary = nil + if Covers(j, s) { + t.Fatal("missing primary was mistaken for identically named secondary") + } + // Label changes and normal resets are not disappearance, but a different + // allowance duration must not silently inherit a pending trigger's coverage. + s.RateLimits.Primary = &w + changed := minutes + 60 + s.RateLimits.Secondary = &codex.Window{WindowDurationMins: &changed} + if Covers(j, s) { + t.Fatal("changed allowance definition inherited coverage") + } +} diff --git a/internal/schedule/quota.go b/internal/schedule/quota.go index 819fd15b..3604ce3a 100644 --- a/internal/schedule/quota.go +++ b/internal/schedule/quota.go @@ -1,7 +1,6 @@ package schedule import ( - "fmt" "github.com/merefield/codexometer/internal/codex" "slices" "time" @@ -10,7 +9,7 @@ import ( func WindowKeys(s codex.Snapshot) []string { var keys []string for _, m := range s.Meters() { - keys = append(keys, fmt.Sprintf("%s/%d/%s", m.LimitID, m.Kind, m.Name)) + keys = append(keys, m.DefinitionKey()) } return keys } diff --git a/internal/ui/benchmark_quota_test.go b/internal/ui/benchmark_quota_test.go index b4ea4789..00a17fac 100644 --- a/internal/ui/benchmark_quota_test.go +++ b/internal/ui/benchmark_quota_test.go @@ -7,16 +7,16 @@ import ( "time" "github.com/merefield/codexometer/internal/codex" + "github.com/merefield/codexometer/internal/quotaestimate" ) func TestSubscriptionBenchmarkAccountingDefersAndIncludesFinalCost(t *testing.T) { now := time.Now() reset := now.Add(7 * 24 * time.Hour).Unix() model := Model{ - snapshot: apiEqSnapshot(10, reset), - quotaAPIAnchors: make(map[string]quotaAPIAnchor), - quotaAPIIssues: make(map[string]string), - quotaAPIAccount: "account-a", + snapshot: apiEqSnapshot(10, reset), + + quotaAPI: quotaestimate.State{Account: "account-a"}, benchmarkQuotaAccounting: benchmarkQuotaAccounting{ billingSource: codex.BenchmarkBillingSubscription, }, @@ -45,7 +45,7 @@ func TestSubscriptionBenchmarkAccountingDefersAndIncludesFinalCost(t *testing.T) model.snapshot = advanced model.observeQuotaAPIEq(advanced, accounting, now.Add(time.Minute)) estimate, ok := model.quotaAPIEstimate(advanced, advanced.Meters()[0]) - if !ok || math.Abs(estimate.fullLow-140.0*100/6) > 1e-9 || math.Abs(estimate.fullHigh-140.0*100/4) > 1e-9 { + if !ok || math.Abs(estimate.FullLow-140.0*100/6) > 1e-9 || math.Abs(estimate.FullHigh-140.0*100/4) > 1e-9 { t.Fatalf("benchmark-inclusive estimate = %#v, %v", estimate, ok) } } @@ -167,10 +167,9 @@ func TestBenchmarkQuotaAccountingSettlesOnlyAfterStableRefresh(t *testing.T) { now := time.Now() snapshot := apiEqSnapshot(10, now.Add(time.Hour).Unix()) model := Model{ - usageFetcher: stubLiveFetcher{}, - snapshot: snapshot, - quotaAPIAnchors: make(map[string]quotaAPIAnchor), - quotaAPIIssues: make(map[string]string), + usageFetcher: stubLiveFetcher{}, + snapshot: snapshot, + benchmarkQuotaAccounting: benchmarkQuotaAccounting{ billingSource: codex.BenchmarkBillingSubscription, settling: true, @@ -209,10 +208,9 @@ func TestStaleQuotaFetchCannotSettleNewBenchmarkAccounting(t *testing.T) { now := time.Now() snapshot := apiEqSnapshot(15, now.Add(time.Hour).Unix()) model := Model{ - usageFetcher: stubLiveFetcher{}, - snapshot: snapshot, - quotaAPIAnchors: make(map[string]quotaAPIAnchor), - quotaAPIIssues: make(map[string]string), + usageFetcher: stubLiveFetcher{}, + snapshot: snapshot, + benchmarkQuotaAccounting: benchmarkQuotaAccounting{ billingSource: codex.BenchmarkBillingSubscription, revision: 2, @@ -228,9 +226,9 @@ func TestStaleQuotaFetchCannotSettleNewBenchmarkAccounting(t *testing.T) { benchmarkQuotaRevision: 1, }) deferred := updated.(Model) - if !deferred.benchmarkQuotaAccounting.deferred() || deferred.quotaAPITelemetryIssue != "OBSERVATION DEFERRED" || len(deferred.quotaAPIEvidence) != 0 { + if !deferred.benchmarkQuotaAccounting.deferred() || deferred.quotaAPITelemetryIssue != "OBSERVATION DEFERRED" || len(deferred.quotaAPI.Evidence) != 0 { t.Fatalf("stale fetch changed benchmark quota learning: accounting=%#v issue=%q evidence=%#v", - deferred.benchmarkQuotaAccounting, deferred.quotaAPITelemetryIssue, deferred.quotaAPIEvidence) + deferred.benchmarkQuotaAccounting, deferred.quotaAPITelemetryIssue, deferred.quotaAPI.Evidence) } } diff --git a/internal/ui/documentation_screenshots_test.go b/internal/ui/documentation_screenshots_test.go index ebb36a04..27052a1d 100644 --- a/internal/ui/documentation_screenshots_test.go +++ b/internal/ui/documentation_screenshots_test.go @@ -104,7 +104,7 @@ func documentationScenes(version string) []documentationScene { if (j+i*3)%11 < 3 { value /= 5 } - s.samples = append(s.samples, monitorSample{at: now.Add(time.Duration(j-35) * 30 * time.Second), duration: 30 * time.Second, intervalTokens: value}) + s.samples = append(s.samples, monitorSample{At: now.Add(time.Duration(j-35) * 30 * time.Second), Duration: 30 * time.Second, Tokens: value}) } sessions.monitorLatest += s.latest sessions.monitorSessionData = append(sessions.monitorSessionData, s) diff --git a/internal/ui/localisation_test.go b/internal/ui/localisation_test.go index feda2646..6dd4d8c0 100644 --- a/internal/ui/localisation_test.go +++ b/internal/ui/localisation_test.go @@ -169,7 +169,7 @@ func TestLocalisedScreensHelper(t *testing.T) { m.observeQuotaAPIEq(m.snapshot, codex.LiveUsageSnapshot{APIEqUSD: float64(step), APIEqPricedCalls: int64(step)}, now.Add(time.Duration(step)*time.Minute)) } estimate, ok := m.quotaAPIEstimate(m.snapshot, m.snapshot.Meters()[0]) - if !ok || estimate.confidence != "MED" { + if !ok || estimate.Confidence != "MED" { t.Fatalf("expected canonical medium confidence, got %+v", estimate) } line := m.quotaAPILine(m.snapshot.Meters()[0], 80) diff --git a/internal/ui/meters.go b/internal/ui/meters.go index a025cf38..fde4baa4 100644 --- a/internal/ui/meters.go +++ b/internal/ui/meters.go @@ -12,6 +12,7 @@ import ( "github.com/merefield/codexometer/internal/codex" "github.com/merefield/codexometer/internal/i18n" + "github.com/merefield/codexometer/internal/quotagraph" ) func usesMeterGrid(view meterViewID) bool { @@ -285,14 +286,8 @@ func renderResetGaugeWithOptions(width, gaugeWidth int, window codex.Window, now } func resetProgress(window codex.Window, now time.Time) (int, bool) { - if window.WindowDurationMins == nil || window.ResetsAt == nil || *window.WindowDurationMins <= 0 { - return 0, false - } - duration := time.Duration(*window.WindowDurationMins) * time.Minute - resetAt := time.Unix(*window.ResetsAt, 0) - startAt := resetAt.Add(-duration) - progress := int(math.Round(float64(now.Sub(startAt)) / float64(duration) * 100)) - return min(max(progress, 0), 100), true + fraction, known := quotagraph.ElapsedFraction(window, now) + return int(math.Round(fraction * 100)), known } func meterColor(used int, colors palette) imagecolor.Color { diff --git a/internal/ui/meters_test.go b/internal/ui/meters_test.go index 93ff0860..8761c76f 100644 --- a/internal/ui/meters_test.go +++ b/internal/ui/meters_test.go @@ -138,6 +138,8 @@ func TestResetProgressUsesWindowStartAndClamps(t *testing.T) { }{ {"before start", reset.Add(-2 * time.Hour), 0}, {"halfway", reset.Add(-30 * time.Minute), 50}, + {"round half up", reset.Add(-30*time.Minute - 18*time.Second), 50}, + {"below rounding boundary", reset.Add(-30*time.Minute - 19*time.Second), 49}, {"at reset", reset, 100}, {"after reset", reset.Add(time.Hour), 100}, } diff --git a/internal/ui/model.go b/internal/ui/model.go index 34b9498f..d3628060 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -14,6 +14,7 @@ import ( "github.com/merefield/codexometer/internal/codex" "github.com/merefield/codexometer/internal/confirmation" "github.com/merefield/codexometer/internal/i18n" + "github.com/merefield/codexometer/internal/quotaestimate" "github.com/merefield/codexometer/internal/quotagraph" "github.com/merefield/codexometer/internal/version" ) @@ -213,12 +214,8 @@ type Model struct { monitorQuotaWindows []monitorQuotaWindow monitorQuotaError string - quotaAPIAnchors map[string]quotaAPIAnchor - quotaAPIEvidence []quotaAPISample - quotaAPIIssues map[string]string - quotaAPIAccount string + quotaAPI quotaestimate.State quotaAPITelemetryIssue string - quotaAPICoverageIssue string } type fetchedMsg struct { @@ -252,11 +249,7 @@ const ( monitorFetchReset ) -type monitorSample struct { - at time.Time - duration time.Duration - intervalTokens int64 -} +type monitorSample = codex.UsageSample type monitorSessionDismissal struct { preview codex.SessionContext @@ -446,8 +439,6 @@ func New(fetcher Fetcher, refreshEvery time.Duration) Model { loading: true, nextRefresh: time.Now().Add(refreshEvery), benchmarkRankMode: benchmarkRankBalanced, - quotaAPIAnchors: make(map[string]quotaAPIAnchor), - quotaAPIIssues: make(map[string]string), appVersion: version.Current(), } if provider, ok := fetcher.(codex.QuotaStepPolicyProvider); ok { @@ -2299,7 +2290,7 @@ func (m Model) fetch() tea.Cmd { case coverageIssue != "": message.usage = after message.usage.APIEqCoverageIssue = coverageIssue - case !quotaAPIAccountingEqual(before, after) || after.APIEqPendingCalls > 0: + case !quotaestimate.AccountingEqual(before, after) || after.APIEqPendingCalls > 0: message.usageErr = errQuotaObservationChanged default: message.usage = after @@ -2534,6 +2525,7 @@ func (m *Model) syncMonitorQuotaSnapshot(snapshot codex.Snapshot) { continue } window := &m.monitorQuotaWindows[index] + window.label = monitorQuotaLabel(meter) if !sameOptionalInt64(window.latestReset, meter.Window.ResetsAt) || meter.Window.UsedPercent < window.latestUsed { window.resetDetected = true } @@ -2549,11 +2541,7 @@ func (m *Model) syncMonitorQuotaSnapshot(snapshot codex.Snapshot) { } func monitorQuotaKey(meter codex.Meter) string { - duration := int64(-1) - if meter.Window.WindowDurationMins != nil { - duration = *meter.Window.WindowDurationMins - } - return meter.Bucket + "\x00" + meter.Name + "\x00" + fmt.Sprint(duration) + return meter.DefinitionKey() } func monitorQuotaLabel(meter codex.Meter) string { @@ -2839,13 +2827,7 @@ func (m *Model) captureMonitorSamples(at time.Time) { if at.IsZero() { at = time.Now() } - start := m.monitorStartedAt - if count := len(m.monitorSamples); count > 0 { - start = m.monitorSamples[count-1].at - } - duration := max(at.Sub(start), time.Duration(0)) - delta := max(m.monitorLatest-m.monitorGraphStart, int64(0)) - m.monitorSamples = appendMonitorSample(m.monitorSamples, monitorSample{at: at, duration: duration, intervalTokens: delta}) + m.monitorSamples = codex.CaptureUsageSample(m.monitorSamples, m.monitorLatest, m.monitorGraphStart, m.monitorStartedAt, at, monitorSampleHistoryMax) m.monitorGraphStart = m.monitorLatest for index := range m.monitorSessionData { @@ -2853,25 +2835,11 @@ func (m *Model) captureMonitorSamples(at time.Time) { if !session.displayed { continue } - start := session.startedAt - if count := len(session.samples); count > 0 { - start = session.samples[count-1].at - } - duration := max(at.Sub(start), time.Duration(0)) - delta := max(session.latest-session.graphStart, int64(0)) - session.samples = appendMonitorSample(session.samples, monitorSample{at: at, duration: duration, intervalTokens: delta}) + session.samples = codex.CaptureUsageSample(session.samples, session.latest, session.graphStart, session.startedAt, at, monitorSampleHistoryMax) session.graphStart = session.latest } } -func appendMonitorSample(samples []monitorSample, sample monitorSample) []monitorSample { - if len(samples) >= monitorSampleHistoryMax { - copy(samples, samples[len(samples)-monitorSampleHistoryMax+1:]) - samples = samples[:monitorSampleHistoryMax-1] - } - return append(samples, sample) -} - func (m *Model) monitorSessionIndex(id string) int { for index := range m.monitorSessionData { if m.monitorSessionData[index].id == id { diff --git a/internal/ui/model_test.go b/internal/ui/model_test.go index 2f7969d5..1e04a3b1 100644 --- a/internal/ui/model_test.go +++ b/internal/ui/model_test.go @@ -512,7 +512,7 @@ func TestQuotaFetchCoverageRecoveryCannotSpanUnobservedGap(t *testing.T) { m := New(f, time.Minute) m.observeQuotaAPIEq(apiEqSnapshot(10, reset), codex.LiveUsageSnapshot{}, now) m.observeQuotaAPIEq(apiEqSnapshot(15, reset), codex.LiveUsageSnapshot{APIEqUSD: 1, APIEqPricedCalls: 1}, now.Add(time.Minute)) - if len(m.quotaAPIEvidence) != 1 { + if len(m.quotaAPI.Evidence) != 1 { t.Fatal("test did not establish an estimate") } message := m.fetch()().(fetchedMsg) @@ -521,7 +521,7 @@ func TestQuotaFetchCoverageRecoveryCannotSpanUnobservedGap(t *testing.T) { } updated, _ := m.Update(message) m = updated.(Model) - if len(m.quotaAPIEvidence) != 0 || len(m.quotaAPIAnchors) != 0 || m.quotaAPICoverageIssue == "" { + if len(m.quotaAPI.Evidence) != 0 || len(m.quotaAPI.Anchors) != 0 || m.quotaAPI.CoverageIssue == "" { t.Fatal("coverage gap retained previous evidence") } // A later clean bracket starts a new baseline even when the recovered @@ -531,7 +531,7 @@ func TestQuotaFetchCoverageRecoveryCannotSpanUnobservedGap(t *testing.T) { f.usages = []codex.LiveUsageSnapshot{recovered, recovered} updated, _ = m.Update(m.fetch()()) m = updated.(Model) - if len(m.quotaAPIEvidence) != 0 || m.quotaAPICoverageIssue != "" { + if len(m.quotaAPI.Evidence) != 0 || m.quotaAPI.CoverageIssue != "" { t.Fatal("recovery learned across the gap") } recovered.APIEqUSD++ @@ -540,8 +540,8 @@ func TestQuotaFetchCoverageRecoveryCannotSpanUnobservedGap(t *testing.T) { f.usages = []codex.LiveUsageSnapshot{recovered, recovered} updated, _ = m.Update(m.fetch()()) m = updated.(Model) - if len(m.quotaAPIEvidence) != 1 || m.quotaAPIEvidence[0].CapacityUSD != 20 { - t.Fatal("next clean interval failed to learn independently", m.quotaAPIEvidence) + if len(m.quotaAPI.Evidence) != 1 || m.quotaAPI.Evidence[0].CapacityUSD != 20 { + t.Fatal("next clean interval failed to learn independently", m.quotaAPI.Evidence) } }) } @@ -584,7 +584,7 @@ func TestQuotaCoverageInvalidationSurvivesObservationErrorsAndBenchmarkDeferral( } updated, _ := m.Update(message) m = updated.(Model) - if len(m.quotaAPIEvidence) != 0 || len(m.quotaAPIAnchors) != 0 || m.quotaAPICoverageIssue == "" { + if len(m.quotaAPI.Evidence) != 0 || len(m.quotaAPI.Anchors) != 0 || m.quotaAPI.CoverageIssue == "" { t.Fatal("deferred observation retained stale capacity evidence") } }) diff --git a/internal/ui/monitor.go b/internal/ui/monitor.go index dc0df77e..30bb2b0d 100644 --- a/internal/ui/monitor.go +++ b/internal/ui/monitor.go @@ -300,7 +300,7 @@ func (m Model) renderMonitorSessionRow(width, height int, session monitorSession } func monitorNeedsAttention(attention codex.SessionAttention) bool { - return attention == codex.SessionAttentionInput || attention == codex.SessionAttentionApproval || attention == codex.SessionAttentionCheck || attention == codex.SessionAttentionError + return codex.SessionNeedsAttention(attention) } func monitorSessionColumnWidths(width int) (int, int, bool) { @@ -497,21 +497,6 @@ func (m Model) renderMonitorSessionBadge(session monitorSession, width int, colo return "" } -func monitorAttentionLabel(attention codex.SessionAttention) string { - switch attention { - case codex.SessionAttentionComplete: - return i18n.Text("TURN COMPLETE") - case codex.SessionAttentionApproval: - return i18n.Text("APPROVAL NEEDED") - case codex.SessionAttentionCheck: - return i18n.Text("CHECK SESSION") - case codex.SessionAttentionError: - return i18n.Text("TURN FAILED") - default: - return i18n.Text("INPUT NEEDED") - } -} - func monitorAttentionStatus(attention codex.SessionAttention) string { switch attention { case codex.SessionAttentionComplete: @@ -718,12 +703,12 @@ func (m Model) renderMonitorGraphWithAction(width, height int, samples []monitor } maximum := int64(0) for _, sample := range samples { - maximum = max(maximum, sample.intervalTokens) + maximum = max(maximum, sample.Tokens) } yMax := niceTokenCeiling(maximum) span := monitorSampleInterval - if len(samples) > 0 && samples[len(samples)-1].duration > 0 { - span = samples[len(samples)-1].duration + if len(samples) > 0 && samples[len(samples)-1].Duration > 0 { + span = samples[len(samples)-1].Duration } spanSeconds := max(int(math.Ceil(span.Seconds())), 1) title := fmt.Sprintf("%d SEC %s // AUTO 0-%s", spanSeconds, heading, formatCompactTokens(yMax)) @@ -746,9 +731,9 @@ func (m Model) renderMonitorGraphWithAction(width, height int, samples []monitor chartWidth := len(samples)*sampleWidth - 1 startX := innerWidth - chartWidth for index, sample := range samples { - ratio := float64(sample.intervalTokens) / float64(yMax) + ratio := float64(sample.Tokens) / float64(yMax) filledRows := int(math.Round(ratio * float64(plotHeight))) - if sample.intervalTokens > 0 { + if sample.Tokens > 0 { filledRows = max(filledRows, 1) } filledRows = min(filledRows, plotHeight) diff --git a/internal/ui/monitor_close_all_test.go b/internal/ui/monitor_close_all_test.go index 6b6df3c2..94ee312b 100644 --- a/internal/ui/monitor_close_all_test.go +++ b/internal/ui/monitor_close_all_test.go @@ -25,7 +25,7 @@ func TestMonitorCloseAllDismissesOffScreenRowsAndPreservesMeasurement(t *testing m := Model{snapshot: codex.DemoSnapshot(), width: 100, height: 24, meterView: viewMonitor, monitorState: monitorRunning, monitorStartedAt: now, monitorBaseline: 1200, monitorLatest: 1320, - monitorSamples: []monitorSample{{intervalTokens: 120}}, + monitorSamples: []monitorSample{{Tokens: 120}}, } m.startMonitorSessions(usage, now) for i := range usage.Sessions { diff --git a/internal/ui/monitor_context.go b/internal/ui/monitor_context.go index a8b246df..12545459 100644 --- a/internal/ui/monitor_context.go +++ b/internal/ui/monitor_context.go @@ -13,10 +13,7 @@ import ( ) func monitorSessionAttentionLabel(s monitorSession) string { - if s.attention == codex.SessionAttentionError { - return i18n.Text(codex.SessionFailureLabel(s.preview)) - } - return monitorAttentionLabel(s.attention) + return i18n.Text(codex.SessionAttentionLabel(s.attention, s.preview)) } func contextTitle(c codex.SessionContext) string { diff --git a/internal/ui/monitor_context_test.go b/internal/ui/monitor_context_test.go index c3729efd..69626f64 100644 --- a/internal/ui/monitor_context_test.go +++ b/internal/ui/monitor_context_test.go @@ -16,7 +16,7 @@ import ( func contextTestModel() Model { m := Model{width: 120, height: 40, meterView: viewMonitor, snapshot: codex.DemoSnapshot(), monitorState: monitorRunning} for _, id := range []string{"root-one", "root-two", "root-three"} { - m.monitorSessionData = append(m.monitorSessionData, monitorSession{id: id, active: true, displayed: true, preview: codex.SessionContext{Kind: codex.SessionContextReply, Text: "Finished updating the documentation.\n次の手順を選んでください。", ThreadID: id, Source: "LOCAL", At: time.Now()}, samples: []monitorSample{{intervalTokens: 100}}}) + m.monitorSessionData = append(m.monitorSessionData, monitorSession{id: id, active: true, displayed: true, preview: codex.SessionContext{Kind: codex.SessionContextReply, Text: "Finished updating the documentation.\n次の手順を選んでください。", ThreadID: id, Source: "LOCAL", At: time.Now()}, samples: []monitorSample{{Tokens: 100}}}) } for i := range m.monitorSessionData { m.monitorSessionData[i].working = true diff --git a/internal/ui/monitor_filter.go b/internal/ui/monitor_filter.go index 7e38b343..73433dcb 100644 --- a/internal/ui/monitor_filter.go +++ b/internal/ui/monitor_filter.go @@ -53,7 +53,7 @@ func (m Model) monitorSessionInList(s monitorSession) bool { // Use the existing live activity signal; a quiet fallback CHECK is not proof of // recent activity, even while its writer process remains open. func monitorSessionRecentlyActive(s monitorSession) bool { - return s.active && s.attention != codex.SessionAttentionCheck && s.attention != codex.SessionAttentionError + return codex.SessionRecentlyActive(s.active, s.attention) } func (m Model) monitorListSessionCount() int { diff --git a/internal/ui/monitor_statusline.go b/internal/ui/monitor_statusline.go index f58f8391..73dd9976 100644 --- a/internal/ui/monitor_statusline.go +++ b/internal/ui/monitor_statusline.go @@ -5,6 +5,7 @@ import ( "github.com/charmbracelet/x/ansi" "github.com/merefield/codexometer/internal/codex" + "github.com/merefield/codexometer/internal/i18n" "github.com/merefield/codexometer/internal/statusline" ) @@ -53,14 +54,7 @@ func (m Model) monitorStatusValues() map[string]string { // Both the detail footer and every telemetry box use the same per-session // values. Rendering an unselected row must never borrow the selected session. func (m Model) monitorStatusValuesFor(s monitorSession) map[string]string { - state := monitorSessionAttentionLabel(s) - if state == "" { - if s.working { - state = "WORKING" - } else { - state = "IDLE" - } - } + state := i18n.Text(codex.SessionStatus(s.active, s.working, s.attention, s.preview)) if m.monitorError != "" || m.monitorState != monitorRunning { state = "STALE" } diff --git a/internal/ui/monitor_test.go b/internal/ui/monitor_test.go index 6fd99d8d..47c0d202 100644 --- a/internal/ui/monitor_test.go +++ b/internal/ui/monitor_test.go @@ -49,7 +49,7 @@ func TestMonitorAutoStartAndResetLifecycle(t *testing.T) { usage: usageWithTokens(1_250), at: startedAt.Add(30*time.Second + 100*time.Millisecond), }) model = updated.(Model) - if len(model.monitorSamples) != 1 || model.monitorSamples[0].intervalTokens != 250 { + if len(model.monitorSamples) != 1 || model.monitorSamples[0].Tokens != 250 { t.Fatalf("30-second graph bucket was not recorded: %#v", model.monitorSamples) } @@ -148,7 +148,7 @@ func TestMonitorTracksRootSessionsAndSamplesEveryGraphOnOneTick(t *testing.T) { if len(model.monitorSessionData[0].samples) != 1 || len(model.monitorSessionData[1].samples) != 1 { t.Fatalf("session graphs were not sampled together: %#v", model.monitorSessionData) } - if model.monitorSessionData[0].samples[0].intervalTokens != 60 || model.monitorSessionData[1].samples[0].intervalTokens != 40 { + if model.monitorSessionData[0].samples[0].Tokens != 60 || model.monitorSessionData[1].samples[0].Tokens != 40 { t.Fatalf("per-root sample deltas were not isolated: %#v", model.monitorSessionData) } } @@ -400,7 +400,7 @@ func TestMonitorAddsANewlyDetectedRootWithoutLosingItsFirstUsage(t *testing.T) { t.Fatalf("new root elapsed time = %s; want its own 500ms lifetime", got) } model.captureMonitorSamples(startedAt.Add(time.Second)) - if sample := model.monitorSessionData[index].samples[0]; sample.duration != 500*time.Millisecond || sample.intervalTokens != 40 { + if sample := model.monitorSessionData[index].samples[0]; sample.Duration != 500*time.Millisecond || sample.Tokens != 40 { t.Fatalf("new root partial first sample = %#v; want 500ms and 40 tokens", sample) } } @@ -429,7 +429,7 @@ func TestMonitorWaitsForAFreshBoundaryReadWhenOrdinaryFetchIsInFlight(t *testing kind: monitorFetchBoundary, sequence: 6, usage: usageWithTokens(125), at: startedAt.Add(30*time.Second + 2*time.Millisecond), }) model = updated.(Model) - if len(model.monitorSamples) != 1 || model.monitorSamples[0].intervalTokens != 125 || model.monitorBoundaryDue { + if len(model.monitorSamples) != 1 || model.monitorSamples[0].Tokens != 125 || model.monitorBoundaryDue { t.Fatalf("fresh boundary result did not close the bucket: %#v", model.monitorSamples) } } @@ -472,8 +472,8 @@ func TestMonitorRendersOneMetricsAndGraphPairPerRootSession(t *testing.T) { model := Model{ monitorState: monitorRunning, monitorStartedAt: time.Now().Add(-time.Minute), monitorSessionData: []monitorSession{ - {id: "019d-alpha-a1b2c", workingDirectory: "/work/alpha", latest: 150, agentCount: 2, active: true, displayed: true, samples: []monitorSample{{intervalTokens: 50}}}, - {id: "019d-bravo-d4e5f", workingDirectory: "/work/bravo", latest: 75, active: true, displayed: true, samples: []monitorSample{{intervalTokens: 25}}}, + {id: "019d-alpha-a1b2c", workingDirectory: "/work/alpha", latest: 150, agentCount: 2, active: true, displayed: true, samples: []monitorSample{{Tokens: 50}}}, + {id: "019d-bravo-d4e5f", workingDirectory: "/work/bravo", latest: 75, active: true, displayed: true, samples: []monitorSample{{Tokens: 25}}}, }, } view := ansi.Strip(model.renderMonitorSessions(116, 24, paletteFor(themeHacker))) @@ -489,10 +489,10 @@ func TestMonitorRendersOneMetricsAndGraphPairPerRootSession(t *testing.T) { func TestMonitorSessionRowsStayInsideResponsiveRectangle(t *testing.T) { sessions := []monitorSession{ - {id: "root-a", active: true, displayed: true, samples: []monitorSample{{intervalTokens: 10}}}, - {id: "root-b", active: true, displayed: true, samples: []monitorSample{{intervalTokens: 20}}}, - {id: "root-c", active: true, displayed: true, samples: []monitorSample{{intervalTokens: 30}}}, - {id: "root-d", active: true, displayed: true, samples: []monitorSample{{intervalTokens: 40}}}, + {id: "root-a", active: true, displayed: true, samples: []monitorSample{{Tokens: 10}}}, + {id: "root-b", active: true, displayed: true, samples: []monitorSample{{Tokens: 20}}}, + {id: "root-c", active: true, displayed: true, samples: []monitorSample{{Tokens: 30}}}, + {id: "root-d", active: true, displayed: true, samples: []monitorSample{{Tokens: 40}}}, } for _, size := range []struct{ width, height int }{{20, 6}, {40, 9}, {80, 12}, {116, 24}} { model := Model{monitorState: monitorRunning, monitorSessionData: sessions} @@ -757,7 +757,7 @@ func TestMonitorViewIsResponsiveAndGraphAutoScales(t *testing.T) { meterView: viewMonitor, monitorState: monitorRunning, monitorBaseline: 1_000, monitorLatest: 7_250, monitorStartedAt: time.Now().Add(-time.Minute), - monitorSamples: []monitorSample{{intervalTokens: 250}, {intervalTokens: 6_000}}, + monitorSamples: []monitorSample{{Tokens: 250}, {Tokens: 6_000}}, } output := model.render() plain := ansi.Strip(output) @@ -937,14 +937,14 @@ func TestMonitorButtonBoxesMatchEnabledHitSurfacesAcrossSizes(t *testing.T) { } func TestMonitorGraphIntroducesBarsOnRightAndScrollsThemLeft(t *testing.T) { - model := Model{monitorState: monitorRunning, monitorSamples: []monitorSample{{intervalTokens: 10}}} + model := Model{monitorState: monitorRunning, monitorSamples: []monitorSample{{Tokens: 10}}} first := ansi.Strip(model.renderMonitorGraph(40, 10, paletteFor(themeHacker))) firstX := graphRightmostBar(first) if firstX < 0 { t.Fatalf("first graph has no bar:\n%s", first) } - model.monitorSamples = append(model.monitorSamples, monitorSample{intervalTokens: 10}) + model.monitorSamples = append(model.monitorSamples, monitorSample{Tokens: 10}) second := ansi.Strip(model.renderMonitorGraph(40, 10, paletteFor(themeHacker))) line := graphBarLine(second) if !strings.Contains(line, "█ █") || graphRightmostBar(second) != firstX { @@ -955,7 +955,7 @@ func TestMonitorGraphIntroducesBarsOnRightAndScrollsThemLeft(t *testing.T) { func TestMonitorGraphUsesVerticalGranularBars(t *testing.T) { model := Model{ monitorState: monitorRunning, - monitorSamples: []monitorSample{{intervalTokens: 25}, {intervalTokens: 100}}, + monitorSamples: []monitorSample{{Tokens: 25}, {Tokens: 100}}, } graph := ansi.Strip(model.renderMonitorGraph(40, 12, paletteFor(themeHacker))) if strings.Count(graph, "█") <= len(model.monitorSamples) { @@ -1006,14 +1006,14 @@ func TestMonitorPollingAdaptsToSessionActivity(t *testing.T) { func TestMonitorSampleHistoryIsBounded(t *testing.T) { samples := make([]monitorSample, monitorSampleHistoryMax) for index := range samples { - samples[index].intervalTokens = int64(index) + samples[index].Tokens = int64(index) } - samples = appendMonitorSample(samples, monitorSample{intervalTokens: 9_999}) + samples = codex.AppendUsageSample(samples, monitorSample{Tokens: 9_999}, monitorSampleHistoryMax) if len(samples) != monitorSampleHistoryMax { t.Fatalf("bounded history length = %d, want %d", len(samples), monitorSampleHistoryMax) } - if samples[0].intervalTokens != 1 || samples[len(samples)-1].intervalTokens != 9_999 { - t.Fatalf("bounded history retained wrong range: first=%d last=%d", samples[0].intervalTokens, samples[len(samples)-1].intervalTokens) + if samples[0].Tokens != 1 || samples[len(samples)-1].Tokens != 9_999 { + t.Fatalf("bounded history retained wrong range: first=%d last=%d", samples[0].Tokens, samples[len(samples)-1].Tokens) } } diff --git a/internal/ui/quota_api_eq.go b/internal/ui/quota_api_eq.go index 1b26b6a3..027c858e 100644 --- a/internal/ui/quota_api_eq.go +++ b/internal/ui/quota_api_eq.go @@ -2,8 +2,6 @@ package ui import ( "fmt" - "math" - "sort" "strings" "time" @@ -11,390 +9,11 @@ import ( "github.com/merefield/codexometer/internal/codex" "github.com/merefield/codexometer/internal/i18n" -) - -const ( - quotaAPIMinimumDelta = 5 - quotaAPIMaxSamples = 12 - quotaAPIMaxAge = 45 * 24 * time.Hour + "github.com/merefield/codexometer/internal/quotaestimate" ) var errQuotaObservationChanged = fmt.Errorf("local accounting changed during quota read") -func quotaAPIAccountingEqual(left, right codex.LiveUsageSnapshot) bool { - return left.APIEqCoverageIssue == right.APIEqCoverageIssue && - left.APIEqUSD == right.APIEqUSD && - left.APIEqTierPremiumUSD == right.APIEqTierPremiumUSD && - left.APIEqUnknownTierCalls == right.APIEqUnknownTierCalls && - left.APIEqPricedCalls == right.APIEqPricedCalls && - left.APIEqUnpricedCalls == right.APIEqUnpricedCalls && - left.APIEqMissingUsageCalls == right.APIEqMissingUsageCalls && - left.APIEqInconsistentUsageCalls == right.APIEqInconsistentUsageCalls && - left.APIEqAccountingAdjustments == right.APIEqAccountingAdjustments && - left.APIEqPendingCalls == right.APIEqPendingCalls -} - -// quotaAPISample is process-local and content-free. Its key contains only an -// in-memory account fingerprint plus the quota-window identity; it retains no -// email, session, model-call, or token-event identifier and is never persisted. -type quotaAPISample struct { - Key string `json:"key"` - CapacityUSD float64 `json:"capacityUsd"` - LowUSD float64 `json:"lowUsd"` - HighUSD float64 `json:"highUsd"` - DeltaPercent int `json:"deltaPercent"` - ObservedAtUnix int64 `json:"observedAt"` - PricingRetrievedOn string `json:"pricingRetrievedOn"` - PremiumCapacityUSD float64 `json:"premiumCapacityUsd"` - UnknownTier bool `json:"unknownTier"` -} - -type quotaAPIAnchor struct { - usedPercent int - resetAt int64 - costUSD float64 - premiumUSD float64 - unknownTierCalls int64 - pricedCalls int64 - unpricedCalls int64 - missingUsageCalls int64 - inconsistentUsageCalls int64 - accountingAdjustments int64 - restartReason string - restartIssue codex.APIEqDiagnostic -} - -const ( - quotaAPIRestartAccountChanged = "ACCOUNT CHANGED" - quotaAPIRestartAccountingRebased = "LOCAL ACCOUNTING REBASED" - quotaAPIRestartCoverageGap = "LOCAL COVERAGE GAP" - quotaAPIRestartUnpricedUsage = "UNPRICED USAGE" - quotaAPIRestartMissingUsage = "MISSING RESPONSE USAGE" - quotaAPIRestartInconsistentUsage = "INCONSISTENT RESPONSE USAGE" - quotaAPIRestartWindowChanged = "WINDOW DEFINITION CHANGED" - quotaAPIRestartWindowReset = "WINDOW RESET" -) - -type quotaAPIEstimate struct { - currentLow float64 - currentHigh float64 - fullLow float64 - fullHigh float64 - samples int - confidence string - standardFull float64 - hasPremium bool - unknownTier bool -} - -// A known coverage gap invalidates evidence even when another error or an -// active benchmark prevents using the associated quota observation. -func (m *Model) observeQuotaAPICoverage(issue string) { - previous := m.quotaAPICoverageIssue - m.quotaAPICoverageIssue = issue - if previous != "" || issue != "" { - m.quotaAPIAnchors = make(map[string]quotaAPIAnchor) - m.quotaAPIEvidence = nil - m.quotaAPIIssues = make(map[string]string) - } -} - -func (m *Model) observeQuotaAPIEq(snapshot codex.Snapshot, usage codex.LiveUsageSnapshot, at time.Time) { - if at.IsZero() { - at = time.Now() - } - account := strings.TrimSpace(snapshot.AccountFingerprint) - if account == "" { - return - } - accountChanged := m.quotaAPIAccount != "" && m.quotaAPIAccount != account - if accountChanged { - m.quotaAPIAnchors = make(map[string]quotaAPIAnchor) - m.quotaAPIEvidence = nil - m.quotaAPIIssues = make(map[string]string) - } - m.quotaAPIAccount = account - m.observeQuotaAPICoverage(usage.APIEqCoverageIssue) - if usage.APIEqCoverageIssue != "" { - return - } - - if m.quotaAPIAnchors == nil { - m.quotaAPIAnchors = make(map[string]quotaAPIAnchor) - } - if m.quotaAPIIssues == nil { - m.quotaAPIIssues = make(map[string]string) - } - priorAnchorKeys := make(map[string]struct{}, len(m.quotaAPIAnchors)) - for key := range m.quotaAPIAnchors { - priorAnchorKeys[key] = struct{}{} - } - m.quotaAPIEvidence = validQuotaAPISamples(m.quotaAPIEvidence, at) - for _, meter := range snapshot.Meters() { - if !quotaAPIMeterEligible(snapshot, meter) { - continue - } - key := quotaAPIKey(snapshot, meter) - current := quotaAPIAnchor{ - usedPercent: min(max(meter.Window.UsedPercent, 0), 100), - resetAt: optionalUnix(meter.Window.ResetsAt), costUSD: usage.APIEqUSD + usage.APIEqTierPremiumUSD, - premiumUSD: usage.APIEqTierPremiumUSD, unknownTierCalls: usage.APIEqUnknownTierCalls, - pricedCalls: usage.APIEqPricedCalls, unpricedCalls: usage.APIEqUnpricedCalls, - missingUsageCalls: usage.APIEqMissingUsageCalls, - inconsistentUsageCalls: usage.APIEqInconsistentUsageCalls, - accountingAdjustments: usage.APIEqAccountingAdjustments, - } - anchor, exists := m.quotaAPIAnchors[key] - if !exists { - switch { - case accountChanged: - current.restartReason = quotaAPIRestartAccountChanged - case quotaAPIHasRelatedPriorAnchor(priorAnchorKeys, snapshot, meter): - current.restartReason = quotaAPIRestartWindowChanged - } - m.quotaAPIAnchors[key] = current - m.quotaAPIIssues[key] = "" - continue - } - if quotaAPIWindowReset(anchor, current, at) { - current.restartReason = quotaAPIRestartWindowReset - m.quotaAPIAnchors[key] = current - m.quotaAPIIssues[key] = "" - continue - } - if current.costUSD < anchor.costUSD || current.pricedCalls < anchor.pricedCalls || - current.unpricedCalls < anchor.unpricedCalls || - current.unknownTierCalls < anchor.unknownTierCalls || - current.missingUsageCalls < anchor.missingUsageCalls || - current.inconsistentUsageCalls < anchor.inconsistentUsageCalls || - current.accountingAdjustments < anchor.accountingAdjustments { - current.restartReason = quotaAPIRestartAccountingRebased - m.quotaAPIAnchors[key] = current - m.quotaAPIIssues[key] = "" - continue - } - // The backend may revise an upcoming rolling-window reset timestamp - // without starting a new quota window. Preserve the earliest known - // boundary so a later observation can still detect that it passed, - // while retaining the clean movement and accounting baselines now. - if current.resetAt > 0 && (anchor.resetAt == 0 || current.resetAt < anchor.resetAt) { - anchor.resetAt = current.resetAt - m.quotaAPIAnchors[key] = anchor - } - if current.unpricedCalls > anchor.unpricedCalls { - current.restartReason = quotaAPIRestartUnpricedUsage - current.restartIssue = latestIntervalDiagnostic(usage, codex.UsageIssueUnpriced, anchor.unpricedCalls) - m.quotaAPIAnchors[key] = current - m.quotaAPIIssues[key] = "" - continue - } - switch { - case current.missingUsageCalls > anchor.missingUsageCalls: - current.restartReason = quotaAPIRestartMissingUsage - current.restartIssue = latestIntervalDiagnostic(usage, codex.UsageIssueMissing, anchor.missingUsageCalls) - case current.inconsistentUsageCalls > anchor.inconsistentUsageCalls: - current.restartReason = quotaAPIRestartInconsistentUsage - current.restartIssue = latestIntervalDiagnostic(usage, codex.UsageIssueInconsistent, anchor.inconsistentUsageCalls) - case current.accountingAdjustments > anchor.accountingAdjustments: - current.restartReason = quotaAPIRestartAccountingRebased - } - if current.restartReason != "" { - m.quotaAPIAnchors[key] = current - m.quotaAPIIssues[key] = "" - continue - } - deltaPercent := current.usedPercent - anchor.usedPercent - if deltaPercent < quotaAPIMinimumDelta { - continue - } - deltaCost := current.costUSD - anchor.costUSD - if deltaCost <= 0 || current.pricedCalls == anchor.pricedCalls { - current.restartReason = quotaAPIRestartCoverageGap - m.quotaAPIAnchors[key] = current - m.quotaAPIIssues[key] = "" - continue - } - capacity := deltaCost * 100 / float64(deltaPercent) - low := deltaCost * 100 / float64(deltaPercent+1) - high := deltaCost * 100 / float64(deltaPercent-1) - m.quotaAPIEvidence = append(m.quotaAPIEvidence, quotaAPISample{ - Key: key, CapacityUSD: capacity, LowUSD: low, HighUSD: high, - DeltaPercent: deltaPercent, ObservedAtUnix: at.Unix(), - PricingRetrievedOn: codex.StandardAPIPricingRetrievedOn, - PremiumCapacityUSD: (current.premiumUSD - anchor.premiumUSD) * 100 / float64(deltaPercent), - UnknownTier: current.unknownTierCalls > anchor.unknownTierCalls, - }) - m.quotaAPIEvidence = trimQuotaAPISamples(m.quotaAPIEvidence, key) - m.quotaAPIAnchors[key] = current - m.quotaAPIIssues[key] = "" - } -} - -func quotaAPIHasRelatedPriorAnchor(priorKeys map[string]struct{}, snapshot codex.Snapshot, meter codex.Meter) bool { - wantedAccount := strings.TrimSpace(snapshot.AccountFingerprint) - wantedLimit := strings.ToLower(strings.TrimSpace(meter.LimitID)) - for key := range priorKeys { - parts := strings.Split(key, "|") - if len(parts) == 4 && parts[0] == wantedAccount && parts[2] == wantedLimit { - return true - } - } - return false -} - -func quotaAPIWindowReset(anchor, current quotaAPIAnchor, observedAt time.Time) bool { - if current.usedPercent < anchor.usedPercent { - return true - } - if anchor.resetAt <= 0 || current.resetAt <= anchor.resetAt { - return false - } - return !observedAt.Before(time.Unix(anchor.resetAt, 0)) -} - -func quotaAPIMeterEligible(snapshot codex.Snapshot, meter codex.Meter) bool { - return meter.Kind == codex.MeterQuotaWindow && snapshot.AccountFingerprint != "" && - strings.EqualFold(strings.TrimSpace(meter.LimitID), "codex") -} - -func optionalUnix(value *int64) int64 { - if value == nil { - return 0 - } - return *value -} - -func quotaAPIKey(snapshot codex.Snapshot, meter codex.Meter) string { - account := strings.TrimSpace(snapshot.AccountFingerprint) - plan := "unknown" - if len(snapshot.RateLimitsByLimitID) > 0 { - if limit, ok := snapshot.RateLimitsByLimitID[meter.LimitID]; ok && limit.PlanType != nil { - plan = strings.ToLower(strings.TrimSpace(*limit.PlanType)) - } - } else if snapshot.RateLimits.PlanType != nil { - plan = strings.ToLower(strings.TrimSpace(*snapshot.RateLimits.PlanType)) - } - duration := int64(0) - if meter.Window.WindowDurationMins != nil { - duration = *meter.Window.WindowDurationMins - } - limitID := strings.ToLower(strings.TrimSpace(meter.LimitID)) - if limitID == "" { - limitID = strings.ToLower(strings.TrimSpace(meter.Bucket)) - } - return fmt.Sprintf("%s|%s|%s|%d", account, plan, limitID, duration) -} - -func validQuotaAPISamples(samples []quotaAPISample, now time.Time) []quotaAPISample { - valid := make([]quotaAPISample, 0, len(samples)) - cutoff := now.Add(-quotaAPIMaxAge).Unix() - for _, sample := range samples { - if sample.Key == "" || sample.PricingRetrievedOn != codex.StandardAPIPricingRetrievedOn || - sample.ObservedAtUnix < cutoff || sample.ObservedAtUnix > now.Add(time.Hour).Unix() || - sample.DeltaPercent < quotaAPIMinimumDelta || !positiveFinite(sample.CapacityUSD) || - !positiveFinite(sample.LowUSD) || !positiveFinite(sample.HighUSD) || - math.IsNaN(sample.PremiumCapacityUSD) || math.IsInf(sample.PremiumCapacityUSD, 0) || - sample.PremiumCapacityUSD >= sample.CapacityUSD || - sample.LowUSD > sample.CapacityUSD || sample.CapacityUSD > sample.HighUSD { - continue - } - valid = append(valid, sample) - } - return valid -} - -func positiveFinite(value float64) bool { - return value > 0 && !math.IsNaN(value) && !math.IsInf(value, 0) -} - -func trimQuotaAPISamples(samples []quotaAPISample, key string) []quotaAPISample { - matching := 0 - for _, sample := range samples { - if sample.Key == key { - matching++ - } - } - remove := matching - quotaAPIMaxSamples - if remove <= 0 { - return samples - } - trimmed := make([]quotaAPISample, 0, len(samples)-remove) - for _, sample := range samples { - if sample.Key == key && remove > 0 { - remove-- - continue - } - trimmed = append(trimmed, sample) - } - return trimmed -} - -func (m Model) quotaAPIEstimate(snapshot codex.Snapshot, meter codex.Meter) (quotaAPIEstimate, bool) { - if m.quotaAPICoverageIssue != "" { - return quotaAPIEstimate{}, false - } - key := quotaAPIKey(snapshot, meter) - matching := make([]quotaAPISample, 0, quotaAPIMaxSamples) - for _, sample := range validQuotaAPISamples(m.quotaAPIEvidence, time.Now()) { - if sample.Key == key { - matching = append(matching, sample) - } - } - if len(matching) == 0 { - return quotaAPIEstimate{}, false - } - lows := make([]float64, 0, len(matching)) - highs := make([]float64, 0, len(matching)) - centers := make([]float64, 0, len(matching)) - standardCenters := make([]float64, 0, len(matching)) - hasPremium, unknownTier := false, false - totalDelta := 0 - for _, sample := range matching { - lows = append(lows, sample.LowUSD) - highs = append(highs, sample.HighUSD) - centers = append(centers, sample.CapacityUSD) - standardCenters = append(standardCenters, sample.CapacityUSD-sample.PremiumCapacityUSD) - hasPremium = hasPremium || sample.PremiumCapacityUSD != 0 - unknownTier = unknownTier || sample.UnknownTier - totalDelta += sample.DeltaPercent - } - fullLow, fullHigh := medianFloat(lows), medianFloat(highs) - center := medianFloat(centers) - minCenter, maxCenter := slicesMinMax(centers) - // Keep this identifier canonical: compact presentations use L/M codes. - confidence := "LOW" - if !unknownTier && len(matching) >= 3 && totalDelta >= 15 && center > 0 && - (fullHigh-fullLow)/center <= 0.45 && (maxCenter-minCenter)/center <= 0.50 { - // Local rollout coverage cannot prove that another machine or client did - // not consume quota, so the estimator intentionally caps at MEDIUM. - confidence = "MED" - } - used := float64(min(max(meter.Window.UsedPercent, 0), 100)) / 100 - return quotaAPIEstimate{ - currentLow: fullLow * used, currentHigh: fullHigh * used, - fullLow: fullLow, fullHigh: fullHigh, samples: len(matching), confidence: confidence, - standardFull: medianFloat(standardCenters), hasPremium: hasPremium, unknownTier: unknownTier, - }, true -} - -func slicesMinMax(values []float64) (float64, float64) { - minimum, maximum := values[0], values[0] - for _, value := range values[1:] { - minimum = min(minimum, value) - maximum = max(maximum, value) - } - return minimum, maximum -} - -func medianFloat(values []float64) float64 { - sort.Float64s(values) - middle := len(values) / 2 - if len(values)%2 == 1 { - return values[middle] - } - return (values[middle-1] + values[middle]) / 2 -} - func (m Model) quotaMetersWithInsights(width int) []codex.Meter { meters := m.snapshot.Meters() lineWidth := max(width-4, 1) @@ -409,7 +28,7 @@ func (m Model) quotaMetersWithInsights(width int) []codex.Meter { line := i18n.Text("API-EQ N/A // LIMIT ATTRIBUTION UNKNOWN") if m.snapshot.AccountFingerprint == "" { line = i18n.Text("API-EQ N/A // ACCOUNT ATTRIBUTION UNKNOWN") - } else if quotaAPIMeterEligible(m.snapshot, meters[index]) { + } else if quotaestimate.Eligible(m.snapshot, meters[index]) { line = m.quotaAPILine(meters[index], lineWidth) } if meters[index].Details == "" { @@ -425,8 +44,8 @@ func (m Model) quotaAPILine(meter codex.Meter, width int) (line string) { if m.benchmarkQuotaAccounting.deferred() { return m.benchmarkQuotaAccounting.deferredLabel(width) } - if m.quotaAPICoverageIssue != "" { - return "API-EQ // " + i18n.Text(quotaAPIRestartCoverageGap) + if m.quotaAPI.CoverageIssue != "" { + return "API-EQ // " + i18n.Text(quotaestimate.RestartCoverageGap) } if m.quotaAPITelemetryIssue != "" { if width < 44 { @@ -436,12 +55,12 @@ func (m Model) quotaAPILine(meter codex.Meter, width int) (line string) { } if estimate, ok := m.quotaAPIEstimate(m.snapshot, meter); ok { basis := "" - if estimate.hasPremium { + if estimate.HasPremium { basis = " TIER*" - if estimate.unknownTier { + if estimate.UnknownTier { basis += "?" } - } else if estimate.unknownTier { + } else if estimate.UnknownTier { basis = " STD?" } // Compact, language-neutral accounting notation. The asterisk is @@ -451,70 +70,67 @@ func (m Model) quotaAPILine(meter codex.Meter, width int) (line string) { if !strings.Contains(line, "API-EQ") { line = strings.Replace(line, "EQ", "EQ"+basis, 1) } - if estimate.hasPremium { - comparison := fmt.Sprintf(" // STD 100%% ~%s", formatUSD(estimate.standardFull)) + if estimate.HasPremium { + comparison := fmt.Sprintf(" // STD 100%% ~%s", formatUSD(estimate.StandardFull)) if ansi.StringWidth(line+comparison) <= width { line += comparison } } }() - current := formatAPIRange(estimate.currentLow, estimate.currentHigh) - full := formatAPIRange(estimate.fullLow, estimate.fullHigh) + current := formatAPIRange(estimate.CurrentLow, estimate.CurrentHigh) + full := formatAPIRange(estimate.FullLow, estimate.FullHigh) switch { case width-len(basis) >= 72: - return i18n.Format("OBSERVED API-EQ // SPEND ~%s // 100%% ~%s // %s · N=%d", current, full, i18n.Text(estimate.confidence), estimate.samples) + return i18n.Format("OBSERVED API-EQ // SPEND ~%s // 100%% ~%s // %s · N=%d", current, full, i18n.Text(estimate.Confidence), estimate.Samples) case width-len(basis) >= 52: - return i18n.Format("API-EQ NOW ~%s // 100%% ~%s · %s%d", current, full, estimate.confidence[:1], estimate.samples) + return i18n.Format("API-EQ NOW ~%s // 100%% ~%s · %s%d", current, full, estimate.Confidence[:1], estimate.Samples) default: - currentMid := formatUSD((estimate.currentLow + estimate.currentHigh) / 2) - fullMid := formatUSD((estimate.fullLow + estimate.fullHigh) / 2) - return i18n.Format("EQ NOW ~%s // FULL ~%s %s%d", currentMid, fullMid, estimate.confidence[:1], estimate.samples) + currentMid := formatUSD((estimate.CurrentLow + estimate.CurrentHigh) / 2) + fullMid := formatUSD((estimate.FullLow + estimate.FullHigh) / 2) + return i18n.Format("EQ NOW ~%s // FULL ~%s %s%d", currentMid, fullMid, estimate.Confidence[:1], estimate.Samples) } } - key := quotaAPIKey(m.snapshot, meter) - if issue := m.quotaAPIIssues[key]; issue != "" { - return i18n.Text("API-EQ LEARNING // ") + issue - } + key := quotaestimate.Key(m.snapshot, meter) progress := 0 - if anchor, ok := m.quotaAPIAnchors[key]; ok { - progress = min(max(meter.Window.UsedPercent-anchor.usedPercent, 0), quotaAPIMinimumDelta) - if anchor.restartReason != "" { - shortReason := shortQuotaAPIRestartReason(anchor.restartReason) + if anchor, ok := m.quotaAPI.Anchors[key]; ok { + progress = min(max(meter.Window.UsedPercent-anchor.UsedPercent, 0), quotaestimate.MinimumDelta) + if anchor.RestartReason != "" { + shortReason := shortQuotaAPIRestartReason(anchor.RestartReason) switch { case width >= 72: - return i18n.Format("API-EQ LEARNING // %d/%dPP CLEAN MOVEMENT // RESTARTED: %s", progress, quotaAPIMinimumDelta, anchor.restartReason) + return i18n.Format("API-EQ LEARNING // %d/%dPP CLEAN MOVEMENT // RESTARTED: %s", progress, quotaestimate.MinimumDelta, anchor.RestartReason) case width >= 44: - return fmt.Sprintf("API-EQ %d/%dPP // RESTARTED: %s", progress, quotaAPIMinimumDelta, shortReason) + return fmt.Sprintf("API-EQ %d/%dPP // RESTARTED: %s", progress, quotaestimate.MinimumDelta, shortReason) default: - return fmt.Sprintf("EQ %d/%dPP // %s", progress, quotaAPIMinimumDelta, shortReason) + return fmt.Sprintf("EQ %d/%dPP // %s", progress, quotaestimate.MinimumDelta, shortReason) } } } if width < 44 { - return i18n.Format("API-EQ LEARNING %d/%dPP", progress, quotaAPIMinimumDelta) + return i18n.Format("API-EQ LEARNING %d/%dPP", progress, quotaestimate.MinimumDelta) } - return i18n.Format("API-EQ LEARNING // %d/%dPP CLEAN MOVEMENT", progress, quotaAPIMinimumDelta) + return i18n.Format("API-EQ LEARNING // %d/%dPP CLEAN MOVEMENT", progress, quotaestimate.MinimumDelta) } // Restart reasons are compact diagnostic codes, not translated labels. Keep // every code canonical so changing language does not partly translate them. func shortQuotaAPIRestartReason(reason string) string { switch reason { - case quotaAPIRestartAccountChanged: + case quotaestimate.RestartAccountChanged: return "ACCOUNT" - case quotaAPIRestartAccountingRebased: + case quotaestimate.RestartAccountingRebased: return "REBASED" - case quotaAPIRestartCoverageGap: + case quotaestimate.RestartCoverageGap: return "COVERAGE GAP" - case quotaAPIRestartUnpricedUsage: + case quotaestimate.RestartUnpricedUsage: return "UNPRICED" - case quotaAPIRestartMissingUsage: + case quotaestimate.RestartMissingUsage: return "USAGE MISSING" - case quotaAPIRestartInconsistentUsage: + case quotaestimate.RestartInconsistentUsage: return "USAGE INCONSISTENT" - case quotaAPIRestartWindowChanged: + case quotaestimate.RestartWindowChanged: return "WINDOW CHANGED" - case quotaAPIRestartWindowReset: + case quotaestimate.RestartWindowReset: return "RESET" default: return reason @@ -544,26 +160,16 @@ func formatUSD(value float64) string { } } -func latestIntervalDiagnostic(usage codex.LiveUsageSnapshot, kind codex.APIEqIssueKind, after int64) codex.APIEqDiagnostic { - for i := len(usage.APIEqDiagnostics) - 1; i >= 0; i-- { - issue := usage.APIEqDiagnostics[i] - if issue.Kind == kind && issue.Counter > after { - return issue - } - } - return codex.APIEqDiagnostic{} -} - func (m Model) quotaAPIDiagnostic() codex.APIEqDiagnostic { if !m.meterView.isQuota() { return codex.APIEqDiagnostic{} } - if m.quotaAPICoverageIssue != "" { - return codex.APIEqDiagnostic{Kind: codex.UsageIssueCoverage, Source: "usage coverage", Reason: m.quotaAPICoverageIssue} + if m.quotaAPI.CoverageIssue != "" { + return codex.APIEqDiagnostic{Kind: codex.UsageIssueCoverage, Source: "usage coverage", Reason: m.quotaAPI.CoverageIssue} } for _, meter := range m.snapshot.Meters() { - if quotaAPIMeterEligible(m.snapshot, meter) { - if issue := m.quotaAPIAnchors[quotaAPIKey(m.snapshot, meter)].restartIssue; issue.Kind != "" { + if quotaestimate.Eligible(m.snapshot, meter) { + if issue := m.quotaAPI.Anchors[quotaestimate.Key(m.snapshot, meter)].RestartIssue; issue.Kind != "" { return issue } } @@ -617,3 +223,11 @@ func (m Model) quotaAPIDiagnosticHeight(width int) int { } return 0 } + +func (m *Model) observeQuotaAPICoverage(issue string) { m.quotaAPI.ObserveCoverage(issue) } +func (m *Model) observeQuotaAPIEq(snapshot codex.Snapshot, usage codex.LiveUsageSnapshot, at time.Time) { + m.quotaAPI.Observe(snapshot, usage, at) +} +func (m Model) quotaAPIEstimate(snapshot codex.Snapshot, meter codex.Meter) (quotaestimate.Estimate, bool) { + return m.quotaAPI.Estimate(snapshot, meter, time.Now()) +} diff --git a/internal/ui/quota_api_eq_test.go b/internal/ui/quota_api_eq_test.go index 5612348f..cd05612f 100644 --- a/internal/ui/quota_api_eq_test.go +++ b/internal/ui/quota_api_eq_test.go @@ -10,20 +10,21 @@ import ( "github.com/merefield/codexometer/internal/codex" "github.com/merefield/codexometer/internal/i18n" + "github.com/merefield/codexometer/internal/quotaestimate" ) func TestShortQuotaAPIRestartReasonsStayCanonical(t *testing.T) { for reason, want := range map[string]string{ - quotaAPIRestartAccountChanged: "ACCOUNT", - quotaAPIRestartAccountingRebased: "REBASED", - quotaAPIRestartCoverageGap: "COVERAGE GAP", - quotaAPIRestartUnpricedUsage: "UNPRICED", - quotaAPIRestartMissingUsage: "USAGE MISSING", - quotaAPIRestartInconsistentUsage: "USAGE INCONSISTENT", - quotaAPIRestartWindowChanged: "WINDOW CHANGED", - quotaAPIRestartWindowReset: "RESET", - "FUTURE REASON": "FUTURE REASON", - "": "", + quotaestimate.RestartAccountChanged: "ACCOUNT", + quotaestimate.RestartAccountingRebased: "REBASED", + quotaestimate.RestartCoverageGap: "COVERAGE GAP", + quotaestimate.RestartUnpricedUsage: "UNPRICED", + quotaestimate.RestartMissingUsage: "USAGE MISSING", + quotaestimate.RestartInconsistentUsage: "USAGE INCONSISTENT", + quotaestimate.RestartWindowChanged: "WINDOW CHANGED", + quotaestimate.RestartWindowReset: "RESET", + "FUTURE REASON": "FUTURE REASON", + "": "", } { if got := shortQuotaAPIRestartReason(reason); got != want { t.Errorf("reason %q: got %q, want canonical %q", reason, got, want) @@ -43,17 +44,17 @@ func TestQuotaAPIEstimatorLearnsRangeAndCurrentSpend(t *testing.T) { model.observeQuotaAPIEq(snapshot, codex.LiveUsageSnapshot{APIEqUSD: 1, APIEqPricedCalls: 1}, now.Add(time.Minute)) meter := snapshot.Meters()[0] estimate, ok := model.quotaAPIEstimate(snapshot, meter) - if !ok || estimate.samples != 1 || estimate.confidence != "LOW" { + if !ok || estimate.Samples != 1 || estimate.Confidence != "LOW" { t.Fatalf("estimate = %#v, %v", estimate, ok) } - if math.Abs(estimate.fullLow-100.0/6) > 1e-9 || math.Abs(estimate.fullHigh-25) > 1e-9 || - math.Abs(estimate.currentLow-2.5) > 1e-9 || math.Abs(estimate.currentHigh-3.75) > 1e-9 { + if math.Abs(estimate.FullLow-100.0/6) > 1e-9 || math.Abs(estimate.FullHigh-25) > 1e-9 || + math.Abs(estimate.CurrentLow-2.5) > 1e-9 || math.Abs(estimate.CurrentHigh-3.75) > 1e-9 { t.Fatalf("estimate range = %#v", estimate) } line := model.quotaAPILine(meter, 100) want := i18n.Format("OBSERVED API-EQ // SPEND ~%s // 100%% ~%s // %s · N=%d", - formatAPIRange(estimate.currentLow, estimate.currentHigh), - formatAPIRange(estimate.fullLow, estimate.fullHigh), + formatAPIRange(estimate.CurrentLow, estimate.CurrentHigh), + formatAPIRange(estimate.FullLow, estimate.FullHigh), i18n.Text("LOW"), 1) if line != want { t.Fatalf("estimate line = %q", line) @@ -70,8 +71,8 @@ func TestQuotaAPIEstimatorRequiresCleanPricedMovement(t *testing.T) { model.observeQuotaAPIEq(apiEqSnapshot(25, reset), codex.LiveUsageSnapshot{ APIEqUSD: 1, APIEqPricedCalls: 1, APIEqUnpricedCalls: 1, }, now.Add(time.Minute)) - if len(model.quotaAPIEvidence) != 0 { - t.Fatalf("unpriced interval produced evidence: %#v", model.quotaAPIEvidence) + if len(model.quotaAPI.Evidence) != 0 { + t.Fatalf("unpriced interval produced evidence: %#v", model.quotaAPI.Evidence) } line := model.quotaAPILine(apiEqSnapshot(25, reset).Meters()[0], 80) if !strings.Contains(line, "UNPRICED USAGE") { @@ -82,8 +83,8 @@ func TestQuotaAPIEstimatorRequiresCleanPricedMovement(t *testing.T) { model.observeQuotaAPIEq(apiEqSnapshot(30, reset), codex.LiveUsageSnapshot{ APIEqUSD: 1, APIEqPricedCalls: 1, APIEqUnpricedCalls: 1, }, now.Add(2*time.Minute)) - if len(model.quotaAPIEvidence) != 0 || !strings.Contains(model.quotaAPILine(apiEqSnapshot(30, reset).Meters()[0], 80), "LOCAL COVERAGE GAP") { - t.Fatalf("quota-only movement was not rejected: evidence=%#v issues=%#v", model.quotaAPIEvidence, model.quotaAPIIssues) + if len(model.quotaAPI.Evidence) != 0 || !strings.Contains(model.quotaAPILine(apiEqSnapshot(30, reset).Meters()[0], 80), "LOCAL COVERAGE GAP") { + t.Fatalf("quota-only movement was not rejected: evidence=%#v", model.quotaAPI.Evidence) } } @@ -104,7 +105,7 @@ func TestQuotaAPIEstimatorResetsAnchorAndCapsConfidence(t *testing.T) { }, now.Add(time.Duration(index+1)*time.Minute)) } estimate, ok := model.quotaAPIEstimate(apiEqSnapshot(used, reset), apiEqSnapshot(used, reset).Meters()[0]) - if !ok || estimate.samples != 3 || estimate.confidence != "MED" { + if !ok || estimate.Samples != 3 || estimate.Confidence != "MED" { t.Fatalf("multi-sample estimate = %#v, %v", estimate, ok) } @@ -112,8 +113,8 @@ func TestQuotaAPIEstimatorResetsAnchorAndCapsConfidence(t *testing.T) { model.observeQuotaAPIEq(apiEqSnapshot(1, newReset), codex.LiveUsageSnapshot{ APIEqUSD: cost, APIEqPricedCalls: calls, }, now.Add(4*time.Minute)) - key := quotaAPIKey(apiEqSnapshot(1, newReset), apiEqSnapshot(1, newReset).Meters()[0]) - if anchor := model.quotaAPIAnchors[key]; anchor.usedPercent != 1 || anchor.resetAt != newReset { + key := quotaestimate.Key(apiEqSnapshot(1, newReset), apiEqSnapshot(1, newReset).Meters()[0]) + if anchor := model.quotaAPI.Anchors[key]; anchor.UsedPercent != 1 || anchor.ResetAt != newReset { t.Fatalf("reset anchor = %#v", anchor) } } @@ -130,9 +131,9 @@ func TestQuotaAPIEstimatorPreservesProgressAcrossUpcomingResetTimeRevisionAndIna usage := codex.LiveUsageSnapshot{APIEqUSD: 1, APIEqPricedCalls: 1} model.snapshot = advanced model.observeQuotaAPIEq(advanced, usage, now.Add(2*time.Hour)) - key := quotaAPIKey(advanced, advanced.Meters()[0]) - anchor := model.quotaAPIAnchors[key] - if anchor.usedPercent != 10 || anchor.resetAt != reset || anchor.restartReason != "" { + key := quotaestimate.Key(advanced, advanced.Meters()[0]) + anchor := model.quotaAPI.Anchors[key] + if anchor.UsedPercent != 10 || anchor.ResetAt != reset || anchor.RestartReason != "" { t.Fatalf("benign reset-time revision moved anchor = %#v", anchor) } if line := model.quotaAPILine(advanced.Meters()[0], 100); !strings.Contains(line, "2/5PP CLEAN MOVEMENT") || strings.Contains(line, "RESTARTED") { @@ -155,10 +156,10 @@ func TestQuotaAPIEstimatorRestartsWhenPreviousResetBoundaryPassed(t *testing.T) rolled := apiEqSnapshot(10, reset+int64((7*24*time.Hour).Seconds())) model.snapshot = rolled model.observeQuotaAPIEq(rolled, codex.LiveUsageSnapshot{}, now.Add(2*time.Minute)) - key := quotaAPIKey(rolled, rolled.Meters()[0]) - anchor := model.quotaAPIAnchors[key] - if anchor.usedPercent != 10 || anchor.resetAt != *rolled.Meters()[0].Window.ResetsAt || - anchor.restartReason != quotaAPIRestartWindowReset { + key := quotaestimate.Key(rolled, rolled.Meters()[0]) + anchor := model.quotaAPI.Anchors[key] + if anchor.UsedPercent != 10 || anchor.ResetAt != *rolled.Meters()[0].Window.ResetsAt || + anchor.RestartReason != quotaestimate.RestartWindowReset { t.Fatalf("rolled-window anchor = %#v", anchor) } if line := model.quotaAPILine(rolled.Meters()[0], 100); !strings.Contains(line, "RESTARTED: WINDOW RESET") { @@ -207,17 +208,17 @@ func TestQuotaAPIEstimatorExplainsWindowDefinitionChange(t *testing.T) { func TestQuotaAPIEstimatorKeepsDivergentSamplesAtLowConfidence(t *testing.T) { now := time.Now() snapshot := apiEqSnapshot(25, now.Add(4*time.Hour).Unix()) - key := quotaAPIKey(snapshot, snapshot.Meters()[0]) + key := quotaestimate.Key(snapshot, snapshot.Meters()[0]) model := New(nil, time.Minute) for index, capacity := range []float64{1, 100, 10_000} { - model.quotaAPIEvidence = append(model.quotaAPIEvidence, quotaAPISample{ + model.quotaAPI.Evidence = append(model.quotaAPI.Evidence, quotaestimate.Sample{ Key: key, CapacityUSD: capacity, LowUSD: capacity * 5 / 6, HighUSD: capacity * 5 / 4, DeltaPercent: 5, ObservedAtUnix: now.Add(time.Duration(index) * time.Minute).Unix(), PricingRetrievedOn: codex.StandardAPIPricingRetrievedOn, }) } estimate, ok := model.quotaAPIEstimate(snapshot, snapshot.Meters()[0]) - if !ok || estimate.confidence != "LOW" { + if !ok || estimate.Confidence != "LOW" { t.Fatalf("divergent estimate = %#v, %v; want LOW", estimate, ok) } } @@ -253,8 +254,8 @@ func TestQuotaAPIEstimatorRestrictsAdditionalLimitsAndIsolatesAccounts(t *testin advanced.RateLimitsByLimitID["spark"] = sparkLimit model.snapshot = advanced model.observeQuotaAPIEq(advanced, codex.LiveUsageSnapshot{APIEqUSD: 1, APIEqPricedCalls: 1}, now.Add(time.Minute)) - if len(model.quotaAPIEvidence) != 1 || model.quotaAPIEvidence[0].Key != quotaAPIKey(advanced, advanced.Meters()[0]) { - t.Fatalf("additional limit received evidence: %#v", model.quotaAPIEvidence) + if len(model.quotaAPI.Evidence) != 1 || model.quotaAPI.Evidence[0].Key != quotaestimate.Key(advanced, advanced.Meters()[0]) { + t.Fatalf("additional limit received evidence: %#v", model.quotaAPI.Evidence) } view := strings.Join([]string{model.quotaAPILine(advanced.Meters()[0], 80), model.quotaMetersWithInsights(100)[1].Details}, "\n") if !strings.Contains(view, "100%") || !strings.Contains(view, "LIMIT ATTRIBUTION UNKNOWN") { @@ -267,8 +268,8 @@ func TestQuotaAPIEstimatorRestrictsAdditionalLimitsAndIsolatesAccounts(t *testin t.Fatal("evidence crossed account fingerprints") } model.observeQuotaAPIEq(otherAccount, codex.LiveUsageSnapshot{APIEqUSD: 1, APIEqPricedCalls: 1}, now.Add(2*time.Minute)) - if len(model.quotaAPIEvidence) != 0 || len(model.quotaAPIAnchors) != 1 || model.quotaAPIAccount != "account-b" { - t.Fatalf("account switch retained prior observations: account=%q anchors=%#v evidence=%#v", model.quotaAPIAccount, model.quotaAPIAnchors, model.quotaAPIEvidence) + if len(model.quotaAPI.Evidence) != 0 || len(model.quotaAPI.Anchors) != 1 || model.quotaAPI.Account != "account-b" { + t.Fatalf("account switch retained prior observations: account=%q anchors=%#v evidence=%#v", model.quotaAPI.Account, model.quotaAPI.Anchors, model.quotaAPI.Evidence) } unknownAccount := apiEqSnapshot(20, reset) @@ -276,25 +277,25 @@ func TestQuotaAPIEstimatorRestrictsAdditionalLimitsAndIsolatesAccounts(t *testin unknownModel := New(nil, time.Minute) unknownModel.snapshot = unknownAccount unknownModel.observeQuotaAPIEq(unknownAccount, codex.LiveUsageSnapshot{}, now) - if len(unknownModel.quotaAPIAnchors) != 0 || + if len(unknownModel.quotaAPI.Anchors) != 0 || !strings.Contains(unknownModel.quotaMetersWithInsights(100)[0].Details, "ACCOUNT ATTRIBUTION UNKNOWN") { - t.Fatalf("unknown account was not fail-closed: anchors=%#v meters=%#v", unknownModel.quotaAPIAnchors, unknownModel.quotaMetersWithInsights(100)) + t.Fatalf("unknown account was not fail-closed: anchors=%#v meters=%#v", unknownModel.quotaAPI.Anchors, unknownModel.quotaMetersWithInsights(100)) } } func TestQuotaAPIAccountingBracketRequiresStableCounters(t *testing.T) { left := codex.LiveUsageSnapshot{APIEqUSD: 1, APIEqPricedCalls: 2, APIEqUnpricedCalls: 3} - if !quotaAPIAccountingEqual(left, left) { + if !quotaestimate.AccountingEqual(left, left) { t.Fatal("identical accounting was unstable") } right := left right.APIEqPricedCalls++ - if quotaAPIAccountingEqual(left, right) { + if quotaestimate.AccountingEqual(left, right) { t.Fatal("changed accounting was accepted") } right = left right.APIEqPendingCalls++ - if quotaAPIAccountingEqual(left, right) { + if quotaestimate.AccountingEqual(left, right) { t.Fatal("pending accounting was accepted") } } @@ -302,10 +303,10 @@ func TestQuotaAPIAccountingBracketRequiresStableCounters(t *testing.T) { func TestQuotaAPITelemetryIssueTakesPrecedenceOverOldEstimate(t *testing.T) { now := time.Now() snapshot := apiEqSnapshot(25, now.Add(4*time.Hour).Unix()) - key := quotaAPIKey(snapshot, snapshot.Meters()[0]) + key := quotaestimate.Key(snapshot, snapshot.Meters()[0]) model := New(nil, time.Minute) model.snapshot = snapshot - model.quotaAPIEvidence = []quotaAPISample{{ + model.quotaAPI.Evidence = []quotaestimate.Sample{{ Key: key, CapacityUSD: 20, LowUSD: 18, HighUSD: 22, DeltaPercent: 5, ObservedAtUnix: now.Unix(), PricingRetrievedOn: codex.StandardAPIPricingRetrievedOn, }} @@ -318,12 +319,12 @@ func TestQuotaAPITelemetryIssueTakesPrecedenceOverOldEstimate(t *testing.T) { func TestQuotaAPIEvidenceRejectsStaleAndDifferentPricing(t *testing.T) { now := time.Now() - valid := quotaAPISample{Key: "pro|codex|300", CapacityUSD: 10, LowUSD: 9, HighUSD: 11, DeltaPercent: 5, ObservedAtUnix: now.Unix(), PricingRetrievedOn: codex.StandardAPIPricingRetrievedOn} + valid := quotaestimate.Sample{Key: "pro|codex|300", CapacityUSD: 10, LowUSD: 9, HighUSD: 11, DeltaPercent: 5, ObservedAtUnix: now.Unix(), PricingRetrievedOn: codex.StandardAPIPricingRetrievedOn} stale := valid - stale.ObservedAtUnix = now.Add(-quotaAPIMaxAge - time.Hour).Unix() + stale.ObservedAtUnix = now.Add(-quotaestimate.MaxAge - time.Hour).Unix() oldPrice := valid oldPrice.PricingRetrievedOn = "2020-01-01" - if got := validQuotaAPISamples([]quotaAPISample{stale, oldPrice, valid}, now); len(got) != 1 || got[0] != valid { + if got := quotaestimate.ValidSamples([]quotaestimate.Sample{stale, oldPrice, valid}, now); len(got) != 1 || got[0] != valid { t.Fatalf("valid samples = %#v", got) } } @@ -331,12 +332,12 @@ func TestQuotaAPIEvidenceRejectsStaleAndDifferentPricing(t *testing.T) { func TestEveryQuotaViewDisplaysResponsiveAPIEqReadout(t *testing.T) { now := time.Now() snapshot := apiEqSnapshot(25, now.Add(4*time.Hour).Unix()) - key := quotaAPIKey(snapshot, snapshot.Meters()[0]) + key := quotaestimate.Key(snapshot, snapshot.Meters()[0]) model := New(stubFetcher{snapshot: snapshot}, time.Minute) model.loading = false model.snapshot = snapshot model.width, model.height = 120, 44 - model.quotaAPIEvidence = []quotaAPISample{{ + model.quotaAPI.Evidence = []quotaestimate.Sample{{ Key: key, CapacityUSD: 20, LowUSD: 18, HighUSD: 22, DeltaPercent: 10, ObservedAtUnix: now.Unix(), PricingRetrievedOn: codex.StandardAPIPricingRetrievedOn, }} @@ -381,16 +382,16 @@ func TestQuotaUsageIssuesRestartWithSpecificReasonsAndRecover(t *testing.T) { switch kind { case "missing": issue.APIEqMissingUsageCalls = 1 - reason = quotaAPIRestartMissingUsage + reason = quotaestimate.RestartMissingUsage case "inconsistent": issue.APIEqInconsistentUsageCalls = 1 - reason = quotaAPIRestartInconsistentUsage + reason = quotaestimate.RestartInconsistentUsage case "adjusted": issue.APIEqAccountingAdjustments = 1 - reason = quotaAPIRestartAccountingRebased + reason = quotaestimate.RestartAccountingRebased case "unpriced": issue.APIEqUnpricedCalls = 1 - reason = quotaAPIRestartUnpricedUsage + reason = quotaestimate.RestartUnpricedUsage } observe := func(percent int, usage codex.LiveUsageSnapshot) { snap := apiEqSnapshot(percent, reset) @@ -399,8 +400,8 @@ func TestQuotaUsageIssuesRestartWithSpecificReasonsAndRecover(t *testing.T) { model.observeQuotaAPIEq(snap, usage, now.Add(time.Duration(percent)*time.Minute)) } observe(15, issue) - if len(model.quotaAPIEvidence) != 0 || !strings.Contains(model.quotaAPILine(model.snapshot.Meters()[0], 120), reason) { - t.Fatal("unsafe learning or incorrect cause", model.quotaAPIEvidence, model.quotaAPIAnchors) + if len(model.quotaAPI.Evidence) != 0 || !strings.Contains(model.quotaAPILine(model.snapshot.Meters()[0], 120), reason) { + t.Fatal("unsafe learning or incorrect cause", model.quotaAPI.Evidence, model.quotaAPI.Anchors) } clean := issue clean.APIEqUSD = 2 @@ -412,8 +413,8 @@ func TestQuotaUsageIssuesRestartWithSpecificReasonsAndRecover(t *testing.T) { clean.APIEqUSD = 3 clean.APIEqPricedCalls = 3 observe(20, clean) - if len(model.quotaAPIEvidence) != 1 { - t.Fatal("clean learning did not resume", model.quotaAPIAnchors) + if len(model.quotaAPI.Evidence) != 1 { + t.Fatal("clean learning did not resume", model.quotaAPI.Anchors) } }) } @@ -428,7 +429,7 @@ func TestQuotaAccountingBracketIncludesUsageIssuesAndAdjustments(t *testing.T) { } { after := before mutate(&after) - if quotaAPIAccountingEqual(before, after) { + if quotaestimate.AccountingEqual(before, after) { t.Fatal("changed usage accounting was accepted") } } @@ -443,8 +444,8 @@ func TestQuotaFlexDiscountLearnsWithoutFalseRebase(t *testing.T) { m.snapshot = apiEqSnapshot(15, reset) m.observeQuotaAPIEq(m.snapshot, codex.LiveUsageSnapshot{APIEqUSD: 2, APIEqTierPremiumUSD: -1, APIEqPricedCalls: 1}, now.Add(time.Minute)) estimate, ok := m.quotaAPIEstimate(m.snapshot, m.snapshot.Meters()[0]) - if !ok || !estimate.hasPremium || math.Abs(estimate.standardFull-40) > 1e-12 { - t.Fatal("discount mistaken for accounting regression", m.quotaAPIAnchors, estimate) + if !ok || !estimate.HasPremium || math.Abs(estimate.StandardFull-40) > 1e-12 { + t.Fatal("discount mistaken for accounting regression", m.quotaAPI.Anchors, estimate) } if !strings.Contains(m.quotaAPILine(m.snapshot.Meters()[0], 120), "TIER*") { t.Fatal("discount lost requested-tier qualifier") @@ -502,13 +503,13 @@ func TestQuotaAPICoverageGapDiscardsEvidenceAndRestartsCleanly(t *testing.T) { m.observeQuotaAPIEq(m.snapshot, codex.LiveUsageSnapshot{}, now) m.snapshot = apiEqSnapshot(15, reset) m.observeQuotaAPIEq(m.snapshot, codex.LiveUsageSnapshot{APIEqUSD: 1, APIEqPricedCalls: 1}, now.Add(time.Minute)) - if len(m.quotaAPIEvidence) != 1 { + if len(m.quotaAPI.Evidence) != 1 { t.Fatal("test failed to learn") } for _, used := range []int{20, 25} { m.snapshot = apiEqSnapshot(used, reset) m.observeQuotaAPIEq(m.snapshot, codex.LiveUsageSnapshot{APIEqUSD: 2, APIEqPricedCalls: 2, APIEqCoverageIssue: "SESSION child: background compaction usage is unobserved"}, now.Add(2*time.Minute)) - if len(m.quotaAPIEvidence) != 0 || len(m.quotaAPIAnchors) != 0 || !strings.Contains(m.quotaAPILine(m.snapshot.Meters()[0], 100), "COVERAGE GAP") { + if len(m.quotaAPI.Evidence) != 0 || len(m.quotaAPI.Anchors) != 0 || !strings.Contains(m.quotaAPILine(m.snapshot.Meters()[0], 100), "COVERAGE GAP") { t.Fatal("incomplete interval learned or showed old estimates") } if _, ok := m.quotaAPIEstimate(m.snapshot, m.snapshot.Meters()[0]); ok { @@ -519,12 +520,12 @@ func TestQuotaAPICoverageGapDiscardsEvidenceAndRestartsCleanly(t *testing.T) { // interval cannot be attributed to the next clean quota movement. m.snapshot = apiEqSnapshot(30, reset) m.observeQuotaAPIEq(m.snapshot, codex.LiveUsageSnapshot{APIEqUSD: 10, APIEqPricedCalls: 3}, now.Add(3*time.Minute)) - if len(m.quotaAPIEvidence) != 0 { + if len(m.quotaAPI.Evidence) != 0 { t.Fatal("recovery learned across blocked interval") } m.snapshot = apiEqSnapshot(35, reset) m.observeQuotaAPIEq(m.snapshot, codex.LiveUsageSnapshot{APIEqUSD: 11, APIEqPricedCalls: 4}, now.Add(4*time.Minute)) - if len(m.quotaAPIEvidence) != 1 || math.Abs(m.quotaAPIEvidence[0].CapacityUSD-20) > 1e-9 { - t.Fatal("clean recovery failed", m.quotaAPIEvidence) + if len(m.quotaAPI.Evidence) != 1 || math.Abs(m.quotaAPI.Evidence[0].CapacityUSD-20) > 1e-9 { + t.Fatal("clean recovery failed", m.quotaAPI.Evidence) } } diff --git a/internal/ui/quota_status.go b/internal/ui/quota_status.go index f864a40c..d2ad9778 100644 --- a/internal/ui/quota_status.go +++ b/internal/ui/quota_status.go @@ -11,6 +11,7 @@ import ( "github.com/merefield/codexometer/internal/codex" "github.com/merefield/codexometer/internal/i18n" + "github.com/merefield/codexometer/internal/quotagraph" ) type quotaHealth int @@ -116,18 +117,11 @@ func windowQuotaHealth(window codex.Window, now time.Time) quotaHealth { if used >= 100 { return quotaHealthExhausted } - if window.WindowDurationMins == nil || window.ResetsAt == nil || *window.WindowDurationMins <= 0 { + elapsed, known := quotagraph.ElapsedFraction(window, now) + if !known || elapsed <= 0 || elapsed >= 1 || used == 0 { return quotaHealthWithoutPace(used) } - duration := time.Duration(*window.WindowDurationMins) * time.Minute - resetAt := time.Unix(*window.ResetsAt, 0) - elapsedDuration := now.Sub(resetAt.Add(-duration)) - if elapsedDuration <= 0 || elapsedDuration >= duration || used == 0 { - return quotaHealthWithoutPace(used) - } - - elapsed := float64(elapsedDuration) / float64(duration) usedFraction := float64(used) / 100 if usedFraction <= elapsed { return quotaHealthClear diff --git a/internal/ui/quota_status_test.go b/internal/ui/quota_status_test.go index 38eb279e..512644be 100644 --- a/internal/ui/quota_status_test.go +++ b/internal/ui/quota_status_test.go @@ -21,6 +21,8 @@ func TestWindowQuotaHealthUsesBurnPaceAgainstResetProgress(t *testing.T) { want quotaHealth }{ {name: "clear with ample quota", used: 20, elapsedPct: 50, want: quotaHealthClear}, + {name: "clear exactly on twenty-percent pace", used: 20, elapsedPct: 20, want: quotaHealthClear}, + {name: "clear exactly on eighty-percent pace", used: 80, elapsedPct: 80, want: quotaHealthClear}, {name: "clear when low quota matches late reset", used: 90, elapsedPct: 95, want: quotaHealthClear}, {name: "watch when low quota is burning too quickly", used: 85, elapsedPct: 80, want: quotaHealthWatch}, {name: "near when projected exhaustion is much earlier than reset", used: 40, elapsedPct: 10, want: quotaHealthNear}, diff --git a/internal/ui/quota_tier_test.go b/internal/ui/quota_tier_test.go index b79cb9e8..259415a8 100644 --- a/internal/ui/quota_tier_test.go +++ b/internal/ui/quota_tier_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/merefield/codexometer/internal/codex" + "github.com/merefield/codexometer/internal/quotaestimate" ) func TestQuotaTierLearningKeepsDenominatorAndStandardComparison(t *testing.T) { @@ -18,7 +19,7 @@ func TestQuotaTierLearningKeepsDenominatorAndStandardComparison(t *testing.T) { m.snapshot = snapshot m.observeQuotaAPIEq(snapshot, codex.LiveUsageSnapshot{APIEqUSD: 1, APIEqTierPremiumUSD: 1, APIEqPricedCalls: 1}, now) e, ok := m.quotaAPIEstimate(snapshot, snapshot.Meters()[0]) - if !ok || !e.hasPremium || e.unknownTier || e.standardFull != 20 || math.Abs(e.fullLow-200.0/6) > 1e-9 || e.fullHigh != 50 { + if !ok || !e.HasPremium || e.UnknownTier || e.StandardFull != 20 || math.Abs(e.FullLow-200.0/6) > 1e-9 || e.FullHigh != 50 { t.Fatalf("estimate %+v", e) } line := m.quotaAPILine(snapshot.Meters()[0], 180) @@ -43,12 +44,12 @@ func TestQuotaUnknownTierFallbackAndAtomicity(t *testing.T) { base := codex.LiveUsageSnapshot{APIEqUSD: 1, APIEqPricedCalls: 1} changed := base changed.APIEqTierPremiumUSD = 1 - if quotaAPIAccountingEqual(base, changed) { + if quotaestimate.AccountingEqual(base, changed) { t.Fatal("premium change ignored") } changed = base changed.APIEqUnknownTierCalls = 1 - if quotaAPIAccountingEqual(base, changed) { + if quotaestimate.AccountingEqual(base, changed) { t.Fatal("tier coverage ignored") } now := time.Now().Truncate(time.Second) diff --git a/internal/ui/shared_projection_test.go b/internal/ui/shared_projection_test.go new file mode 100644 index 00000000..eb3af5a6 --- /dev/null +++ b/internal/ui/shared_projection_test.go @@ -0,0 +1,67 @@ +package ui + +import ( + "strings" + "testing" + "time" + + "github.com/merefield/codexometer/internal/codex" + "github.com/merefield/codexometer/internal/i18n" +) + +func TestStatusLineDoesNotInventInputForWorkingOrIdle(t *testing.T) { + m := Model{monitorState: monitorRunning} + for _, working := range []bool{false, true} { + s := monitorSession{id: "synthetic", active: true, working: working} + want := "IDLE" + if working { + want = "WORKING" + } + if got := m.monitorStatusValuesFor(s)["run-state"]; got != i18n.Text(want) { + t.Fatalf("working=%v: %q", working, got) + } + } +} + +func TestMonitorQuotaRenameRefreshesLabelsWithoutRebasing(t *testing.T) { + label := "Original" + snapshot := monitorQuotaSnapshot(20, 1800000000) + snapshot.RateLimits.LimitName = &label + m := Model{monitorState: monitorRunning, monitorStartedAt: time.Unix(1_700_000_000, 0)} + m.startMonitorQuotaSnapshot(snapshot) + original := m.monitorQuotaWindows[0] + + label = "Renamed" + snapshot.RateLimits.Primary.UsedPercent = 23 + m.syncMonitorQuotaSnapshot(snapshot) + if len(m.monitorQuotaWindows) != 1 { + t.Fatal("rename created a new quota window", m.monitorQuotaWindows) + } + window := m.monitorQuotaWindows[0] + if window.key != original.key || window.baselineUsed != 20 || window.latestUsed != 23 || + !sameOptionalInt64(window.latestReset, original.latestReset) || window.partial || window.resetDetected || window.stale { + t.Fatalf("rename changed accounting: before=%+v after=%+v", original, window) + } + if got := m.monitorQuotaReadout(); !strings.Contains(got, "RENAMED 5 HOURS +3PP") || strings.Contains(got, "ORIGINAL") { + t.Fatalf("account quota label did not refresh: %q", got) + } + if got := m.monitorSessionQuotaEstimate(1); !strings.Contains(got, "RENAMED 5H ~3PP") || strings.Contains(got, "ORIGINAL") { + t.Fatalf("session estimate label did not refresh: %q", got) + } +} + +func TestMonitorQuotaIdentityDoesNotUseLabels(t *testing.T) { + minutes := int64(300) + a := codex.Meter{LimitID: "a", Bucket: "Same", Name: "5 HOURS", Window: codex.Window{WindowDurationMins: &minutes}} + b := a + b.LimitID = "b" + if monitorQuotaKey(a) == monitorQuotaKey(b) { + t.Fatal("distinct backend allowances collided") + } + b = a + b.Bucket = "Renamed" + b.Name = "Changed label" + if monitorQuotaKey(a) != monitorQuotaKey(b) { + t.Fatal("display change changed identity") + } +} diff --git a/internal/ui/usage_reports.go b/internal/ui/usage_reports.go index c8fd6faa..95189f0c 100644 --- a/internal/ui/usage_reports.go +++ b/internal/ui/usage_reports.go @@ -10,20 +10,19 @@ import ( "github.com/merefield/codexometer/internal/codex" ) -var reportDimensions = []string{"surface", "model", "feature", "task start"} +var reportDimensions = codex.UsageDimensions() func usageReportValueStyle(colors palette, known bool, percent float64) lipgloss.Style { - if !known { + switch codex.UsageWarningLevel(known, percent) { + case "unknown": return colors.dimmed() + case "full": + return colors.header().Foreground(colors.danger) + case "near": + return colors.header().Foreground(colors.warning) + default: + return colors.header() } - style := colors.header() - if percent >= 100 { - return style.Foreground(colors.danger) - } - if percent >= 80 { - return style.Foreground(colors.warning) - } - return style } func (m Model) renderUsageReport(width, height int, colors palette, lines []string) string { @@ -38,6 +37,7 @@ func (m Model) renderUsageReport(width, height int, colors palette, lines []stri var rows []codex.UsageCategory total := 0.0 knownEmpty := false + var distribution codex.UsageDistribution if reports == nil { lines = append(lines, dim("Account breakdowns unavailable. Token history remains available.")) } else if m.history.mode == 7 { @@ -53,8 +53,8 @@ func (m Model) renderUsageReport(width, height int, colors palette, lines []stri i := len(r.Days) - 1 - min(m.history.offset, len(r.Days)-1) day := r.Days[i] total = day.Total - rows = codex.UsageCategories(day.Groups, dimension) - knownEmpty = day.Total == 0 && day.Groups[dimension] != nil + distribution = codex.DailyUsageDistribution(day, dimension) + rows, knownEmpty = distribution.Rows, distribution.KnownEmpty lines = append(lines, strong(fmt.Sprintf("%s // %.2f %s // %s", day.Date, total, unit, strings.ToUpper(dimension)))) } else { lines = append(lines, dim("No daily buckets reported in the requested 30 days.")) @@ -91,25 +91,8 @@ func (m Model) renderUsageReport(width, height int, colors palette, lines []stri if !p.AccountingComplete { lines = append(lines, warn("PARTIAL ACCOUNTING // totals may still change")) } - wireDimension := dimension - if dimension == "feature" { - wireDimension = "thread_source" - } - if dimension == "task start" { - wireDimension = "turn_trigger" - } - groups := map[string]map[string]float64{} - for _, b := range p.Breakdowns { - if b.Dimension == wireDimension { - if groups[dimension] == nil { - groups[dimension] = map[string]float64{} - } - for _, v := range b.Rows { - groups[dimension][v.Key] += v.BasisPoints / 100 - } - } - } - rows = codex.UsageCategories(groups, dimension) + distribution = codex.WindowUsageDistribution(p, dimension) + rows = distribution.Rows lines = append(lines, dim("BY "+strings.ToUpper(dimension)+" // historical allowance, not today's limit")) } else { lines = append(lines, dim("No quota windows reported.")) @@ -142,10 +125,7 @@ func (m Model) renderUsageReport(width, height int, colors palette, lines []stri } capacity := max(height-len(lines)-1, 0) start := min(m.history.rowOffset, max(len(rows)-capacity, 0)) - peak := total - for _, row := range rows { - peak = max(peak, row.Value) - } + peak := distribution.Peak for index, row := range rows[start:min(start+capacity, len(rows))] { name := ansi.Truncate(row.Name, nameWidth, "") name += strings.Repeat(" ", max(nameWidth-ansi.StringWidth(name), 0)) diff --git a/internal/web/contracts_test.go b/internal/web/contracts_test.go new file mode 100644 index 00000000..c3fa3437 --- /dev/null +++ b/internal/web/contracts_test.go @@ -0,0 +1,133 @@ +package web + +// Generate only explicitly browser-safe projections. Raw client/context types +// contain capabilities and must never be roots of this schema. +import ( + "encoding/json" + "fmt" + "os" + "reflect" + "sort" + "strings" + "testing" + "time" + "unicode" + + "github.com/merefield/codexometer/internal/codex" + "github.com/merefield/codexometer/internal/quotagraph" +) + +func browserContract() string { + names := map[reflect.Type]string{ + reflect.TypeFor[state](): "Snapshot", reflect.TypeFor[session](): "Session", + reflect.TypeFor[goal](): "SessionGoal", reflect.TypeFor[meter](): "Meter", + reflect.TypeFor[credit](): "Credit", reflect.TypeFor[sample](): "Sample", + reflect.TypeFor[codex.AccountUsage](): "Usage", reflect.TypeFor[quotaThreshold](): "Threshold", + reflect.TypeFor[commandMenu](): "CommandMenu", reflect.TypeFor[codex.SessionCommandChoice](): "CommandChoice", + reflect.TypeFor[actionOffer](): "ActionOffer", reflect.TypeFor[scheduledJob](): "ScheduledJob", + reflect.TypeFor[quotagraph.Plot](): "QuotaPlot", + } + definitions := map[string]string{} + var value func(reflect.Type, bool) string + var fields func(reflect.Type) string + fields = func(typ reflect.Type) (out string) { + for i := 0; i < typ.NumField(); i++ { + f := typ.Field(i) + if f.PkgPath != "" { + continue + } + if f.Anonymous { + out += fields(f.Type) + continue + } + tag, options, _ := strings.Cut(f.Tag.Get("json"), ",") + if tag == "-" { + continue + } + if tag == "" { + tag = f.Name + } + optional := "" + if strings.Contains(options, "omitempty") { + optional = "?" + } + out += fmt.Sprintf(" %s%s: %s;\n", tag, optional, value(f.Type, f.Tag.Get("ts") == "nonnull")) + } + return out + } + value = func(typ reflect.Type, nonnull bool) string { + if typ == reflect.TypeFor[time.Time]() { + return "string" + } + switch typ.Kind() { + case reflect.Pointer: + return value(typ.Elem(), false) + " | null" + case reflect.Slice: + out := "Array<" + value(typ.Elem(), false) + ">" + if !nonnull { + out += " | null" + } + return out + case reflect.Array: + return "Array<" + value(typ.Elem(), false) + ">" + case reflect.Map: + return "Record" + case reflect.Struct: + name := names[typ] + if name == "" { + runes := []rune(typ.Name()) + runes[0] = unicode.ToUpper(runes[0]) + name = string(runes) + names[typ] = name + } + if _, seen := definitions[name]; !seen { + definitions[name] = "" + definitions[name] = "export interface " + name + " {\n" + fields(typ) + "}\n" + } + return name + case reflect.Bool: + return "boolean" + case reflect.String: + return "string" + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: + return "number" + default: + panic("unsupported browser contract: " + typ.String()) + } + } + for _, typ := range []reflect.Type{reflect.TypeFor[state](), reflect.TypeFor[commandMenu](), reflect.TypeFor[actionOffer](), reflect.TypeFor[scheduledJob]()} { + value(typ, false) + } + ordered := make([]string, 0, len(definitions)) + for name := range definitions { + ordered = append(ordered, name) + } + sort.Strings(ordered) + out := "// Code generated by make web-contracts; DO NOT EDIT.\n\n" + for _, name := range ordered { + out += definitions[name] + "\n" + } + commands, _ := json.MarshalIndent(codex.LocalSessionCommands(), "", " ") + dimensions, _ := json.Marshal(codex.UsageDimensions()) + return out + "export const localCommands: CommandChoice[] = " + string(commands) + ";\n\n" + + "export const usageDimensions: string[] = " + string(dimensions) + ";\n" +} + +func TestBrowserContract(t *testing.T) { + const path = "../../web/src/contracts.generated.ts" + want := browserContract() + if os.Getenv("CODEXOMETER_WEB_GENERATE") == "1" { + if err := os.WriteFile(path, []byte(want), 0o644); err != nil { + t.Fatal(err) + } + } + got, err := os.ReadFile(path) + if err != nil || string(got) != want { + t.Fatal("browser contract is stale; run make web-contracts", err) + } + for _, secret := range []string{"approvalToken:", "accountFingerprint:", "token:", "params:", "account:"} { + if strings.Contains(want, secret) { + t.Fatal("internal capability leaked into browser contract", secret) + } + } +} diff --git a/internal/web/control_commands.go b/internal/web/control_commands.go index bac370bb..b65a68ed 100644 --- a/internal/web/control_commands.go +++ b/internal/web/control_commands.go @@ -19,6 +19,11 @@ type commandRequest struct { Refresh bool `json:"refresh,omitempty"` } +type commandMenu struct { + codex.SessionCommandMenu + InputReview bool `json:"inputReview,omitempty"` +} + func (c *control) handleCommands(w http.ResponseWriter, r *http.Request, body actionRequest) { q := body.Command if c.commands == nil || q == nil || body.Review != "" || body.Offer != "" || body.Choice != nil || len(body.Answers) != 0 || body.Schedule != nil || body.EditID != "" || body.SendID != "" || body.CancelID != "" || !codex.ValidSessionCommandPath(q.Path) || len(q.Revision) > 128 || len(q.Choice) > 128 || (q.Mode != "list" && q.Mode != "prepare" && q.Mode != "commit") || q.Refresh && q.Mode != "list" { @@ -76,7 +81,8 @@ func (c *control) handleCommands(w http.ResponseWriter, r *http.Request, body ac return } if q.Mode == "list" { - _ = json.NewEncoder(w).Encode(m) + _, _, review := codex.SessionCommandInputValue(m.Path) + _ = json.NewEncoder(w).Encode(commandMenu{m, review}) return } if m.Revision != q.Revision { diff --git a/internal/web/dist/assets/index-BcNMhG4h.js b/internal/web/dist/assets/index-BcNMhG4h.js new file mode 100644 index 00000000..ae7f31e2 --- /dev/null +++ b/internal/web/dist/assets/index-BcNMhG4h.js @@ -0,0 +1,50 @@ +(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=Array.isArray,t=Array.prototype.indexOf,n=Array.prototype.includes,r=Array.from,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyDescriptors,s=Object.prototype,c=Array.prototype,l=Object.getPrototypeOf,u=Object.isExtensible;function d(e){return typeof e==`function`}var f=()=>{};function p(e){return e()}function m(e){for(var t=0;t{e=n,t=r}),resolve:e,reject:t}}function g(e,t,n=!1){return e===void 0?n?t():t:e}function _(e,t){if(Array.isArray(e))return e;if(t===void 0||!(Symbol.iterator in e))return Array.from(e);let n=[];for(let r of e)if(n.push(r),n.length===t)break;return n}var v=1024,y=2048,b=4096,x=8192,S=16384,C=32768,w=1<<25,T=65536,E=1<<19,D=1<<20,ee=1<<25,te=1<<21,O=1<<22,ne=1<<23,k=Symbol(`$state`),re=Symbol(`component`),ie=Symbol(`legacy props`),ae=Symbol(``),oe=Symbol(`attributes`),se=Symbol(`class`),A=Symbol(`style`),ce=Symbol(`text`),le=Symbol(`form reset`),ue=new class extends Error{name=`StaleReactionError`;message="The reaction that called `getAbortSignal()` was re-run or destroyed"},de=!!globalThis.document?.contentType&&globalThis.document.contentType.includes(`xml`),fe={},j=Symbol(`uninitialized`),pe=`http://www.w3.org/1999/xhtml`;function me(){console.warn(`https://svelte.dev/e/derived_inert`)}function he(e){console.warn(`https://svelte.dev/e/hydration_mismatch`)}function ge(){console.warn(`https://svelte.dev/e/select_multiple_invalid_value`)}function _e(){console.warn(`https://svelte.dev/e/svelte_boundary_reset_noop`)}var M=!1;function ve(e){M=e}var N;function ye(e){if(e===null)throw he(),fe;return N=e}function be(){return ye(on(N))}function P(e){if(M){if(on(N)!==null)throw he(),fe;N=e}}function xe(e=1){if(M){for(var t=e,n=N;t--;)n=on(n);N=n}}function Se(e=!0){for(var t=0,n=N;;){if(n.nodeType===8){var r=n.data;if(r===`]`){if(t===0)return n;--t}else(r===`[`||r===`[!`||r[0]===`[`&&!isNaN(Number(r.slice(1))))&&(t+=1)}var i=on(n);e&&n.remove(),n=i}}function Ce(e){if(!e||e.nodeType!==8)throw he(),fe;return e.data}function we(e){return e===this.v}function Te(e,t){return e==e?e!==t||typeof e==`object`&&!!e||typeof e==`function`:t==t}function Ee(e){return!Te(e,this.v)}function De(e){throw Error(`https://svelte.dev/e/lifecycle_outside_component`)}function Oe(){throw Error(`https://svelte.dev/e/async_derived_orphan`)}function ke(e,t,n){throw Error(`https://svelte.dev/e/each_key_duplicate`)}function Ae(e){throw Error(`https://svelte.dev/e/effect_in_teardown`)}function je(){throw Error(`https://svelte.dev/e/effect_in_unowned_derived`)}function Me(e){throw Error(`https://svelte.dev/e/effect_orphan`)}function Ne(){throw Error(`https://svelte.dev/e/effect_update_depth_exceeded`)}function Pe(e){throw Error(`https://svelte.dev/e/props_invalid_value`)}function Fe(){throw Error(`https://svelte.dev/e/state_descriptors_fixed`)}function Ie(){throw Error(`https://svelte.dev/e/state_prototype_fixed`)}function Le(){throw Error(`https://svelte.dev/e/state_unsafe_mutation`)}function Re(){throw Error(`https://svelte.dev/e/svelte_boundary_reset_onerror`)}var ze=!1;function Be(){ze=!0}var Ve=null;function He(e){Ve=e}function Ue(e,t=!1,n){Ve={p:Ve,i:!1,c:null,e:null,s:e,x:null,r:G,l:ze&&!t?{s:null,u:null,$:[]}:null}}function We(e){var t=Ve,n=t.e;if(n!==null){t.e=null;for(var r of n)yn(r)}return e!==void 0&&(t.x=e),t.i=!0,Ve=t.p,Ge(e)}function Ge(e={}){return i(e,re,{value:!0}),e}function Ke(){return!ze||Ve!==null&&Ve.l===null}var qe=[];function Je(){var e=qe;qe=[],m(e)}function Ye(e){if(qe.length===0&&!Ct){var t=qe;queueMicrotask(()=>{t===qe&&Je()})}qe.push(e)}function Xe(){for(;qe.length>0;)Je()}var Ze=~(y|b|v);function Qe(e,t){e.f=e.f&Ze|t}function $e(e){e.f&512||e.deps===null?Qe(e,v):Qe(e,b)}function et(e,t,n){e.f&2048?t.add(e):e.f&4096&&n.add(e),Qe(e,v)}function tt(e){M&&an(e)!==null&&sn(e)}var nt=!1;function rt(){nt||(nt=!0,document.addEventListener(`reset`,e=>{Promise.resolve().then(()=>{if(!e.defaultPrevented)for(let t of e.target.elements)t[le]?.()})},{capture:!0}))}function it(e){var t=W,n=G;Un(null),Wn(null);try{return e()}finally{Un(t),Wn(n)}}function at(e,t,n,r=n){e.addEventListener(t,()=>it(n));let i=e[le];e[le]=i?()=>{i(),r(!0)}:()=>r(!0),rt()}function ot(e,t,n,r){let i=Ke()?ut:pt;var a=e.filter(e=>!e.settled),o=t.map(i);if(n.length===0&&a.length===0){r(o);return}var s=G,c=st(),l=a.length===1?a[0].promise:a.length>1?Promise.all(a.map(e=>e.promise)):null;function u(e){if(!(s.f&16384)){c();try{r([...o,...e])}catch(e){fn(e,s)}ct()}}var d=lt();if(n.length===0){l.then(()=>u([])).finally(d);return}function f(){Promise.all(n.map(e=>ft(e))).then(u).catch(e=>fn(e,s)).finally(d)}l?l.then(()=>{c(),f(),ct()}):f()}function st(){var e=G,t=W,n=Ve,r=I;return function(i=!0){Wn(e),Un(t),He(n),i&&!(e.f&16384)&&(r?.activate(),r?.apply())}}function ct(e=!0){Wn(null),Un(null),He(null),e&&I?.deactivate()}function lt(){var e=G,t=e.b,n=I,r=!!t?.is_rendered();return t?.update_pending_count(1,n),n.increment(r,e),()=>{t?.update_pending_count(-1,n),n.decrement(r,e)}}function ut(e){var t=2|y;return G!==null&&(G.f|=E),{ctx:Ve,deps:null,effects:null,equals:we,f:t,fn:e,reactions:null,rv:0,v:j,wv:0,parent:G,ac:null}}var dt=Symbol(`obsolete`);function ft(e,t,n){let r=G;r===null&&Oe();var i=void 0,a=Bt(j),o=!W,s=new Set;return Cn(()=>{var t=G,n=h();i=n.promise;try{Promise.resolve(e()).then(n.resolve,e=>{e!==ue&&n.reject(e)}).finally(ct)}catch(e){n.reject(e),ct()}var c=I;if(o){if(t.f&32768)var l=lt();if(r.b?.is_rendered())c.async_deriveds.get(t)?.reject(dt);else for(let e of s.values())e.reject(dt);s.add(n),c.async_deriveds.set(t,n)}let u=(e,t=void 0)=>{l?.(),s.delete(n),t!==dt&&(c.activate(),t?(a.f|=ne,Wt(a,t)):(a.f&8388608&&(a.f^=ne),Wt(a,e)),c.deactivate())};n.promise.then(u,e=>u(null,e||`unknown`))}),_n(()=>{for(let e of s)e.reject(dt)}),new Promise(e=>{function t(n){function r(){n===i?e(a):t(i)}n.then(r,r)}t(i)})}function F(e){let t=ut(e);return Kn(t),t}function pt(e){let t=ut(e);return t.equals=Ee,t}function mt(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;n{t.ac.abort(ue),t.ac=null}),t.fn!==null&&(t.teardown=f),sr(t,0),On(t))}function vt(e){if(e.effects!==null)for(let t of e.effects)t.teardown&&t.fn!==null&&cr(t)}var yt=null,I=null,bt=null,xt=null,St=null,Ct=!1,wt=!1,Tt=null,Et=null,Dt=0,Ot=1,kt=class e{id=Ot++;#e=!1;linked=!0;#t=null;#n=null;async_deriveds=new Map;current=new Map;previous=new Map;#r=new Set;#i=new Set;#a=0;#o=new Map;#s=null;#c=[];#l=[];#u=new Set;#d=new Set;#f=new Map;#p=new Set;is_fork=!1;#m=!1;constructor(){yt===null?yt=this:(yt.#n=this,this.#t=yt),yt=this}#h(){if(this.is_fork)return!0;for(let n of this.#o.keys()){for(var e=n,t=!1;e.parent!==null;){if(this.#f.has(e)){t=!0;break}e=e.parent}if(!t)return!0}return!1}skip_effect(e){this.#f.has(e)||this.#f.set(e,{d:[],m:[]}),this.#p.delete(e)}unskip_effect(e,t=e=>this.schedule(e)){var n=this.#f.get(e);if(n){this.#f.delete(e);for(var r of n.d)Qe(r,y),t(r);for(r of n.m)Qe(r,b),t(r)}this.#p.add(e)}#g(){var e=[];for(let i of this.#c)if(!(i.f&16384||!(i.f&6144))){for(var t=i,n=!1;t.parent!==null;){t=t.parent;var r=t.f;if(r&96){if(!(r&1024)){n=!0;break}t.f^=v}}n||e.push(t)}return this.#c=[],e}#_(){this.#e=!0;for(let e of this.#u)this.#d.delete(e),Qe(e,y),this.schedule(e);for(let e of this.#d)Qe(e,b),this.schedule(e);this.apply();for(var t=Tt=[],n=[],r=Et=[];this.#c.length>0;){Dt++>1e3&&(this.#S(),jt());for(let e of this.#g())try{this.#v(e,t,n)}catch(t){throw It(e),this.#h()||this.discard(),t}}if(I=null,r.length>0){var i=e.ensure();for(let e of r)i.schedule(e)}if(Tt=null,Et=null,this.#h()){this.#x(n),this.#x(t);for(let[e,t]of this.#f)Ft(e,t);r.length>0&&I.#_();return}let a=this.#y();if(a)this.#x(n),this.#x(t),a.#b(this);else{this.#u.clear(),this.#d.clear();for(let e of this.#r)e(this);this.#r.clear(),bt=this,Nt(n),Nt(t),bt=null,this.#s?.resolve();var o=I;if(this.#a===0&&(this.#c.length===0||o!==null)&&this.#S(),this.#c.length>0){if(o!==null){for(let e of this.#c)o.#c.push(e);this.#c=[]}else o=this}o!==null&&(Rt.clear(),o.#_())}}#v(e,t,n){e.f^=v;for(var r=e.first;r!==null;){var i=r.f,a=!!(i&96);if(!(a&&i&1024||i&8192||this.#f.has(r))&&r.fn!==null){a?r.f^=v:i&4?t.push(r):nr(r)&&(i&16&&this.#d.add(r),cr(r));var o=r.first;if(o!==null){r=o;continue}}for(;r!==null;){var s=r.next;if(s!==null){r=s;break}r=r.parent}}}#y(){for(var e=this.#t;e!==null;){if(!e.is_fork){for(let[t,[,n]]of this.current)if(e.current.has(t)&&!n)return e}e=e.#t}return null}#b(e){for(let[t,n]of e.current)!this.previous.has(t)&&e.previous.has(t)&&this.previous.set(t,e.previous.get(t)),this.current.set(t,n);for(let[t,n]of e.async_deriveds){let e=this.async_deriveds.get(t);e&&n.promise.then(e.resolve).catch(e.reject)}e.async_deriveds.clear(),this.transfer_effects(e.#u,e.#d);let t=e=>{var n=e.reactions;if(n!==null&&!(e.f&2&&!(e.f&6144)))for(let e of n){var r=e.f;if(r&2)t(e);else{var i=e;r&4194320&&!this.async_deriveds.has(i)&&(this.#d.delete(i),Qe(i,y),this.schedule(i))}}};for(let e of this.current.keys())t(e);this.oncommit(()=>e.discard()),e.#S(),I=this,this.#_()}#x(e){for(var t=0;t{this.#m=!1,this.linked&&this.flush()}))}transfer_effects(e,t){for(let t of e)this.#u.add(t);for(let e of t)this.#d.add(e);e.clear(),t.clear()}oncommit(e){this.#r.add(e)}ondiscard(e){this.#i.add(e)}settled(){return(this.#s??=h()).promise}static ensure(){if(I===null){let t=I=new e;!wt&&!Ct&&Ye(()=>{t.#e||t.flush()})}return I}apply(){xt=null}schedule(e){St=e,e.b?.is_pending&&e.f&16777228&&!(e.f&32768)?e.b.defer_effect(e):this.#c.push(e)}#S(){if(this.linked){var e=this.#t,t=this.#n;e===null||(e.#n=t),t===null?yt=e:t.#t=e,this.linked=!1}}};function At(e){var t=Ct;Ct=!0;try{var n;for(e&&(I!==null&&!I.is_fork&&I.flush(),n=e());;){if(Xe(),I===null)return n;I.flush()}}finally{Ct=t}}function jt(){try{Ne()}catch(e){fn(e,St)}}var Mt=null;function Nt(e){var t=e.length;if(t!==0){for(var n=0;n0)){Rt.clear();for(let e of Mt){if(e.f&24576)continue;let t=[e],n=e.parent;for(;n!==null;)Mt.has(n)&&(Mt.delete(n),t.push(n)),n=n.parent;for(let e=t.length-1;e>=0;e--){let n=t[e];n.f&24576||cr(n)}}Mt.clear()}}Mt=null}}function Pt(e){I.schedule(e)}function Ft(e,t){if(!(e.f&32&&e.f&1024)){e.f&2048?t.d.push(e):e.f&4096&&t.m.push(e),Qe(e,v);for(var n=e.first;n!==null;)Ft(n,t),n=n.next}}function It(e){Qe(e,v);for(var t=e.first;t!==null;)It(t),t=t.next}var Lt=new Set,Rt=new Map,zt=!1;function Bt(e,t){return{f:0,v:e,reactions:null,equals:we,rv:0,wv:0}}function L(e,t){let n=Bt(e,t);return Kn(n),n}function Vt(e,t=!1,n=!0){let r=Bt(e);return t||(r.equals=Ee),ze&&n&&Ve!==null&&Ve.l!==null&&(Ve.l.s??=[]).push(r),r}function R(e,t,n=!1){return W!==null&&(!Hn||W.f&131072)&&Ke()&&W.f&4325394&&(Gn===null||!Gn.has(e))&&Le(),Wt(e,n?Yt(t):t,Et)}var Ht=null,Ut=0;function Wt(e,t,n=null){if(!e.equals(t)){Bn?Rt.set(e,t):Rt.has(e)||Rt.set(e,e.v);var r=kt.ensure();if(r.capture(e,t),e.f&2){let t=e;e.f&2048&&ht(t),xt===null&&$e(t)}e.wv=tr(),Ht=null,Ut=0,Jt(e,y,n),Ht=null,Ke()&&G!==null&&G.f&1024&&!(G.f&96)&&(Yn===null?Xn([e]):Yn.push(e)),!r.is_fork&&Lt.size>0&&!zt&&Gt()}return t}function Gt(){zt=!1;for(let e of Lt){e.f&1024&&Qe(e,b);let t;try{t=nr(e)}catch{t=!0}t&&cr(e)}Lt.clear()}function Kt(e,t=1){var n=K(e),r=t===1?n++:n--;return R(e,n),r}function qt(e){R(e,e.v+1)}function Jt(e,t,n){var r=e.reactions;if(r!==null){var i=Ke(),a=r.length;if(Ut+=a,Ut>1e5&&Ht===null&&(Ht=new Set),Ht!==null){if(Ht.has(e))return;Ht.add(e)}for(var o=0;o{if($n===d)return e();var t=W,n=$n;Un(null),er(d);var r=e();return Un(t),er(n),r};return i&&r.set(`length`,L(t.length,u)),new Proxy(t,{defineProperty(e,t,n){`value`in n&&n.configurable!==!1&&n.enumerable!==!1&&n.writable!==!1||Fe();var i=r.get(t);return i===void 0?f(()=>{var e=L(n.value,u);return r.set(t,e),e}):R(i,n.value,!0),!0},deleteProperty(e,t){var n=r.get(t);if(n===void 0){if(t in e){let e=f(()=>L(j,u));r.set(t,e),qt(o)}}else R(n,j),qt(o);return!0},get(e,n,i){if(n===k)return t;var o=r.get(n),s=n in e;if(o===void 0&&(!s||a(e,n)?.writable)&&(o=f(()=>L(Yt(s?e[n]:j),u)),r.set(n,o)),o!==void 0){var c=K(o);return c===j?void 0:c}return Reflect.get(e,n,i)},getOwnPropertyDescriptor(e,t){this.has?.(e,t);var n=Reflect.getOwnPropertyDescriptor(e,t),i=r.get(t);if(i!==void 0){var a=K(i);if(a===j)return;if(n&&`value`in n)n.value=a;else return{enumerable:!0,configurable:!0,value:a,writable:!0}}return n},has(e,t){if(t===k)return!0;var n=r.get(t),i=n!==void 0&&n.v!==j||Reflect.has(e,t);return(n!==void 0||G!==null&&(!i||a(e,t)?.writable))&&(n===void 0&&(n=f(()=>L(i?Yt(e[t]):j,u)),r.set(t,n)),K(n)===j)?!1:i},set(e,t,n,s){var c=r.get(t),l=t in e;if(i&&t===`length`)for(var d=n;dL(j,u)),r.set(d+``,p)):R(p,j)}if(c===void 0)(!l||a(e,t)?.writable)&&(c=f(()=>L(void 0,u)),R(c,Yt(n)),r.set(t,c));else{l=c.v!==j;var m=f(()=>Yt(n));R(c,m)}var h=Reflect.getOwnPropertyDescriptor(e,t);if(h?.set&&h.set.call(s,n),!l){if(i&&typeof t==`string`){var g=r.get(`length`),_=Number(t);Number.isInteger(_)&&_>=g.v&&R(g,_+1)}qt(o)}return!0},ownKeys(e){K(o);var t=Reflect.ownKeys(e).filter(e=>{var t=r.get(e);return t===void 0||t.v!==j});for(var[n,i]of r)i.v!==j&&!(n in e)&&t.push(n);return t},setPrototypeOf(){Ie()}})}function Xt(e){try{if(typeof e==`object`&&e&&k in e)return e[k]}catch{}return e}function Zt(e,t){return Object.is(Xt(e),Xt(t))}var Qt,$t,en,tn;function nn(){if(Qt===void 0){Qt=window,$t=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,n=Text.prototype;en=a(t,`firstChild`).get,tn=a(t,`nextSibling`).get,u(e)&&(e[se]=void 0,e[oe]=null,e[A]=void 0,e.__e=void 0),u(n)&&(n[ce]=void 0)}}function rn(e=``){return document.createTextNode(e)}function an(e){return en.call(e)}function on(e){return tn.call(e)}function z(e,t){if(!M)return an(e);var n=an(N);if(n===null)n=N.appendChild(rn());else if(t&&n.nodeType!==3){var r=rn();return n?.before(r),ye(r),r}return t&&un(n),ye(n),n}function B(e,t=!1){if(!M){var n=an(e);return n instanceof Comment&&n.data===``?on(n):n}if(t){if(N?.nodeType!==3){var r=rn();return N?.before(r),ye(r),r}un(N)}return N}function V(e,t=!1){if(!M)return an(e);var n=z(e,t);return P(e),n}function H(e,t=1,n=!1){let r=M?N:e;for(var i;t--;)i=r,r=on(r);if(!M)return r;if(n){if(r?.nodeType!==3){var a=rn();return r===null?i?.after(a):r.before(a),ye(a),a}un(r)}return ye(r),r}function sn(e){e.textContent=``}function cn(){return!1}function ln(e,t,n){return t==null||t===`http://www.w3.org/1999/xhtml`?n?document.createElement(e,{is:n}):document.createElement(e):n?document.createElementNS(t,e,{is:n}):document.createElementNS(t,e)}function un(e){if(e.nodeValue.length<65536)return;let t=e.nextSibling;for(;t!==null&&t.nodeType===3;)t.remove(),e.nodeValue+=t.nodeValue,t=e.nextSibling}function dn(e){var t=G;if(t===null)return W.f|=ne,e;if(!(t.f&32768)&&!(t.f&4))throw e;fn(e,t)}function fn(e,t){if(!(t!==null&&t.f&16384)){for(;t!==null;){if(t.f&128&&!(t.f&33570816)){if(!(t.f&32768))throw e;try{t.b.error(e);return}catch(t){e=t}}t=t.parent}throw e}}function pn(e){G===null&&(W===null&&Me(e),je()),Bn&&Ae(e)}function mn(e,t){var n=t.last;n===null?t.last=t.first=e:(n.next=e,e.prev=n,t.last=e)}function hn(e,t){var n=G;n!==null&&n.f&8192&&(e|=x);var r={ctx:Ve,deps:null,nodes:null,f:e|y|512,first:null,fn:t,last:null,next:null,parent:n,b:n&&n.b,prev:null,teardown:null,wv:0,ac:null};I?.register_created_effect(r);var i=r;if(e&4)Tt===null?kt.ensure().schedule(r):Tt.push(r);else if(t!==null){try{cr(r)}catch(e){throw An(r),e}i.deps===null&&i.teardown===null&&i.nodes===null&&i.first===i.last&&!(i.f&524288)&&(i=i.first,e&16&&e&65536&&i!==null&&(i.f|=T))}if(i!==null&&(i.parent=n,n!==null&&mn(i,n),W!==null&&W.f&2&&!(e&64))){var a=W;(a.effects??=[]).push(i)}return r}function gn(){return W!==null&&!Hn}function _n(e){let t=hn(8,null);return Qe(t,v),t.teardown=e,t}function vn(e){pn(`$effect`);var t=G.f;if(!W&&t&32&&Ve!==null&&!Ve.i){var n=Ve;(n.e??=[]).push(e)}else return yn(e)}function yn(e){return hn(4|D,e)}function bn(e){return pn(`$effect.pre`),hn(8|D,e)}function xn(e){kt.ensure();let t=hn(64|E,e);return(e={})=>new Promise(n=>{e.outro?Nn(t,()=>{An(t),n(void 0)}):(An(t),n(void 0))})}function Sn(e){return hn(4,e)}function Cn(e){return hn(O|E,e)}function wn(e,t=0){return hn(8|t,e)}function U(e,t=[],n=[],r=[]){ot(r,t,n,t=>{hn(8,()=>{e(...t.map(K))})})}function Tn(e,t=0){return hn(16|t,e)}function En(e){return hn(32|E,e)}function Dn(e){var t=e.teardown;if(t!==null){let n=Bn,r=W;Vn(!0),Un(null);try{t.call(null)}catch(t){fn(t,e.parent)}finally{Vn(n),Un(r)}}}function On(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){let e=n.ac;e!==null&&it(()=>{e.abort(ue)});var r=n.next;n.f&64?n.parent=null:An(n,t),n=r}}function kn(e){for(var t=e.first;t!==null;){var n=t.next;t.f&32||An(t),t=n}}function An(e,t=!0){var n=!1;(t||e.f&262144)&&e.nodes!==null&&e.nodes.end!==null&&(jn(e.nodes.start,e.nodes.end),n=!0),e.f|=w,On(e,t&&!n),sr(e,0);var r=e.nodes&&e.nodes.t;if(r!==null)for(let e of r)e.stop();Dn(e),e.f^=w,e.f|=S;var i=e.parent;i!==null&&i.first!==null&&Mn(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes=e.ac=e.b=null}function jn(e,t){for(;e!==null;){var n=e===t?null:on(e);e.remove(),e=n}}function Mn(e){var t=e.parent,n=e.prev,r=e.next;n!==null&&(n.next=r),r!==null&&(r.prev=n),t!==null&&(t.first===e&&(t.first=r),t.last===e&&(t.last=n))}function Nn(e,t,n=!0){var r=[];e.f|=256,Pn(e,r,!0);var i=()=>{n&&An(e),t&&t()},a=r.length;if(a>0){var o=()=>--a||i();for(var s of r)s.out(o)}else i()}function Pn(e,t,n){if(!(e.f&8192)){e.f^=x;var r=e.nodes&&e.nodes.t;if(r!==null)for(let e of r)(e.is_global||n)&&t.push(e);for(var i=e.first;i!==null;){var a=i.next;if(!(i.f&64)){var o=!!(i.f&65536)||!!(i.f&32)&&!!(e.f&16);Pn(i,t,o?n:!1)}i=a}}}function Fn(e){e.f&=-257,In(e,!0)}function In(e,t){if(!(e.f&256)&&e.f&8192){e.f^=x,e.f&1024||(Qe(e,y),kt.ensure().schedule(e));for(var n=e.first;n!==null;){var r=n.next,i=!!(n.f&65536)||!!(n.f&32);In(n,i?t:!1),n=r}var a=e.nodes&&e.nodes.t;if(a!==null)for(let e of a)(e.is_global||t)&&e.in()}}function Ln(e,t){if(e.nodes)for(var n=e.nodes.start,r=e.nodes.end;n!==null;){var i=n===r?null:on(n);t.append(n),n=i}}var Rn=null,zn=!1,Bn=!1;function Vn(e){Bn=e}var W=null,Hn=!1;function Un(e){W=e}var G=null;function Wn(e){G=e}var Gn=null;function Kn(e){W!==null&&(W.f&2097152||W.f&2)&&(Gn??=new Set).add(e)}var qn=null,Jn=0,Yn=null;function Xn(e){Yn=e}var Zn=1,Qn=0,$n=Qn;function er(e){$n=e}function tr(){return++Zn}function nr(e){var t=e.f;if(t&2048)return!0;if(t&4096){for(var n=e.deps,r=n.length,i=0;ie.wv)return!0}t&512&&xt===null&&Qe(e,v)}return!1}function rr(e,t,n=!0){var r=e.reactions;if(r!==null&&!(Gn!==null&&Gn.has(e)))for(var i=0;i{e.ac.abort(ue)}),e.ac=null);try{e.f|=te;var u=e.fn,d=u();e.f|=C;var f=ar(e);if(Ke()&&Yn!==null&&!Hn&&f!==null&&!(e.f&6146))for(var p=0;p0)for(t.length=Jn+qn.length,r=0;r{s.ac.abort(ue),s.ac=null,Qe(s,y)}),_t(s),sr(s,0)}}function sr(e,t){var n=e.deps;if(n!==null)for(var r=t;rn?.call(this,e))}return e.startsWith(`pointer`)||e.startsWith(`touch`)||e===`wheel`?(i.__removed=!1,Ye(()=>{i.__removed||t.addEventListener(e,i,r)})):t.addEventListener(e,i,r),i}function xr(e,t,n,r,i){var a={capture:r,passive:i},o=br(e,t,n,a);(t===document.body||t===window||t===document||t instanceof HTMLMediaElement)&&_n(()=>{o.__removed=!0,t.removeEventListener(e,o,a)})}function q(e,t,n){(t[_r]??={})[e]=n}function Sr(e){for(var t=0;t{wr=!1,Cr=null}));var s=0,c=Cr===e&&e[_r];if(c){var l=a.indexOf(c);if(l!==-1&&(t===document||t===window)){e[_r]=t;return}var u=a.indexOf(t);if(u===-1)return;l<=u&&(s=l)}if(o=a[s]||e.target,o!==t){i(e,`currentTarget`,{configurable:!0,get(){return o||n}});var d=W,f=G;Un(null),Wn(null);try{for(var p,m=[];o!==null&&o!==t;){try{var h=o[_r]?.[r];h==null||o.disabled&&e.target!==o||h.call(o,e)}catch(e){p?m.push(e):p=e}if(e.cancelBubble)break;s++,o=s{throw e});throw p}}finally{e[_r]=t,delete e.currentTarget,Un(d),Wn(f)}}}var Er=globalThis?.window?.trustedTypes&&globalThis.window.trustedTypes.createPolicy(`svelte-trusted-html`,{createHTML:e=>e});function Dr(e){return Er?.createHTML(e)??e}function Or(e){var t=ln(`template`);return t.innerHTML=Dr(e.replaceAll(``,``)),t.content}function kr(e,t){var n=G;n.nodes===null&&(n.nodes={start:e,end:t,a:null,t:null})}function J(e,t){var n=!!(t&1),r=!!(t&2),i,a=!e.startsWith(``);return()=>{if(M)return kr(N,null),N;i===void 0&&(i=Or(a?e:``+e),n||(i=an(i)));var t=r||$t?document.importNode(i,!0):i.cloneNode(!0);if(n){var o=an(t),s=t.lastChild;kr(o,s)}else kr(t,t);return t}}function Ar(e,t,n=`svg`){var r=!e.startsWith(``),i=!!(t&1),a=`<${n}>${r?e:``+e}`,o;return()=>{if(M)return kr(N,null),N;if(!o){var e=an(Or(a));if(i)for(o=document.createDocumentFragment();an(e);)o.appendChild(an(e));else o=an(e)}var t=o.cloneNode(!0);if(i){var n=an(t),r=t.lastChild;kr(n,r)}else kr(t,t);return t}}function jr(e,t){return Ar(e,t,`svg`)}function Mr(e=``){if(!M){var t=rn(e+``);return kr(t,t),t}var n=N;return n.nodeType===3?un(n):(n.before(n=rn()),ye(n)),kr(n,n),n}function Nr(){if(M)return kr(N,null),N;var e=document.createDocumentFragment(),t=document.createComment(``),n=rn();return e.append(t,n),kr(t,n),e}function Y(e,t){if(M){var n=G;n.f&32768&&n.nodes.end!==null||(n.nodes.end=N),be()}else e!==null&&e.before(t)}function Pr(){if(M&&N&&N.nodeType===8&&N.textContent?.startsWith(`$`)){let e=N.textContent.substring(1);return be(),e}return(window.__svelte??={}).uid??=1,`c${window.__svelte.uid++}`}function Fr(e){let t=0,n=Bt(0),r;return()=>{gn()&&(K(n),wn(()=>(t===0&&(r=fr(()=>e(()=>qt(n)))),t+=1,()=>{Ye(()=>{--t,t===0&&(r?.(),r=void 0,qt(n))})})))}}var Ir=T|E;function Lr(e,t,n,r){new Rr(e,t,n,r)}var Rr=class{parent;is_pending=!1;transform_error;#e;#t=M?N:null;#n;#r;#i;#a=null;#o=null;#s=null;#c=null;#l=0;#u=0;#d=!1;#f=new Set;#p=new Set;#m=null;#h=Fr(()=>(this.#m=Bt(this.#l),()=>{this.#m=null}));constructor(e,t,n,r){this.#e=e,this.#n=t,this.#r=e=>{var t=G;t.b=this,t.f|=128,n(e)},this.parent=G.b,this.transform_error=r??this.parent?.transform_error??(e=>e),this.#i=Tn(()=>{if(M){let e=this.#t;be();let t=e.data===`[!`;if(e.data.startsWith(`[?`)){let t=JSON.parse(e.data.slice(2));this.#_(t)}else t?this.#y():this.#g()}else this.#b()},Ir),M&&(this.#e=N)}#g(){try{this.#a=En(()=>this.#r(this.#e))}catch(e){this.error(e)}}#_(e){let t=this.#n.failed,{reset:n,invoke_onerror:r}=this.#v(e);Ye(r),t&&(this.#s=En(()=>{t(this.#e,()=>e,()=>n)}))}#v(e){var t=!1,n=!1;let r=()=>{t?_e():(t=!0,n&&Re(),this.#s!==null&&Nn(this.#s,()=>{this.#s=null}),this.#S(()=>{this.#b()}))};return{reset:r,invoke_onerror:()=>{try{n=!0,this.#n.onerror?.(e,r),n=!1}catch(e){fn(e,this.#i&&this.#i.parent)}}}}#y(){let e=this.#n.pending;e&&(this.is_pending=!0,this.#o=En(()=>e(this.#e)),Ye(()=>{var e=this.#c=document.createDocumentFragment(),t=rn(),n=!1;e.append(t),this.#a=this.#S(()=>{try{return En(()=>this.#r(t))}catch(e){try{this.error(e),n=!0}catch(e){fn(e,this.#i.parent)}return null}}),this.#a===null?(this.#c=null,n&&this.#x(I)):this.#u===0&&(this.#e.before(e),this.#c=null,Nn(this.#o,()=>{this.#o=null}),this.#x(I))}))}#b(){try{if(this.is_pending=this.has_pending_snippet(),this.#u=0,this.#l=0,this.#a=En(()=>{this.#r(this.#e)}),this.#u>0){var e=this.#c=document.createDocumentFragment();Ln(this.#a,e);let t=this.#n.pending;this.#o=En(()=>t(this.#e))}else this.#x(I)}catch(e){this.error(e)}}#x(e){this.is_pending=!1,e.transfer_effects(this.#f,this.#p)}defer_effect(e){et(e,this.#f,this.#p)}is_rendered(){return!this.is_pending&&(!this.parent||this.parent.is_rendered())}has_pending_snippet(){return!!this.#n.pending}#S(e){var t=G,n=W,r=Ve;Wn(this.#i),Un(this.#i),He(this.#i.ctx);try{return kt.ensure(),e()}finally{Wn(t),Un(n),He(r)}}#C(e,t){this.has_pending_snippet()?(this.#u+=e,this.#u===0&&(this.#x(t),this.#o&&Nn(this.#o,()=>{this.#o=null}),this.#c&&=(this.#e.before(this.#c),null))):this.parent&&this.parent.#C(e,t)}update_pending_count(e,t){this.#C(e,t),this.#l+=e,!(!this.#m||this.#d)&&(this.#d=!0,Ye(()=>{this.#d=!1,this.#m&&Wt(this.#m,this.#l)}))}get_effect_pending(){return this.#h(),K(this.#m)}error(e){if(!this.#n.onerror&&!this.#n.failed)throw e;I?.is_fork?(this.#a&&I.skip_effect(this.#a),this.#o&&I.skip_effect(this.#o),this.#s&&I.skip_effect(this.#s),I.oncommit(()=>{this.#w(e)})):this.#w(e)}#w(e){this.#a&&=(An(this.#a),null),this.#o&&=(An(this.#o),null),this.#s&&=(An(this.#s),null),M&&(ye(this.#t),xe(),ye(Se()));let t=this.#n.failed,n=e=>{let{reset:n,invoke_onerror:r}=this.#v(e);r(),t&&(this.#s=this.#S(()=>{try{return En(()=>{var r=G;r.b=this,r.f|=128,t(this.#e,()=>e,()=>n)})}catch(e){return fn(e,this.#i.parent),null}}))};Ye(()=>{var t;try{t=this.transform_error(e)}catch(e){fn(e,this.#i&&this.#i.parent);return}typeof t==`object`&&t&&typeof t.then==`function`?t.then(n,e=>fn(e,this.#i&&this.#i.parent)):n(t)})}};function X(e,t){var n=t==null?``:typeof t==`object`?`${t}`:t;n!==(e[ce]??=e.nodeValue)&&(e[ce]=n,e.nodeValue=`${n}`)}function zr(e,t){return Vr(e,t)}var Br=new Map;function Vr(e,{target:t,anchor:n,props:i={},events:a,context:o,intro:s=!0,transformError:c}){nn();var l=void 0,u=xn(()=>{var s=n??t.appendChild(rn());Lr(s,{pending:()=>{}},t=>{Ue({});var n=Ve;if(o&&(n.c=o),a&&(i.$$events=a),M&&kr(t,null),l=e(t,i)||Ge(),M&&(G.nodes.end=N,N===null||N.nodeType!==8||N.data!==`]`))throw he(),fe;We()},c);var u=new Set,d=e=>{for(var n=0;n{for(var e of u)for(let n of[t,document]){var r=Br.get(n),i=r.get(e);--i==0?(n.removeEventListener(e,Tr),r.delete(e),r.size===0&&Br.delete(n)):r.set(e,i)}yr.delete(d),s!==n&&s.parentNode?.removeChild(s)}});return Hr.set(l,u),l}var Hr=new WeakMap,Ur=class{anchor;#e=new Map;#t=new Map;#n=new Map;#r=new Set;#i=!0;constructor(e,t=!0){this.anchor=e,this.#i=t}#a=e=>{if(this.#e.has(e)){var t=this.#e.get(e),n=this.#t.get(t);if(n)Fn(n),this.#r.delete(t);else{var r=this.#n.get(t);r&&(Fn(r.effect),this.#t.set(t,r.effect),this.#n.delete(t),r.fragment.lastChild.remove(),this.anchor.before(r.fragment),n=r.effect)}for(let[t,n]of this.#e){if(this.#e.delete(t),t===e)break;let r=this.#n.get(n);r&&(An(r.effect),this.#n.delete(n))}for(let[e,r]of this.#t){if(e===t||this.#r.has(e))continue;let i=()=>{if(Array.from(this.#e.values()).includes(e)){var t=document.createDocumentFragment();Ln(r,t),t.append(rn()),this.#n.set(e,{effect:r,fragment:t})}else An(r);this.#r.delete(e),this.#t.delete(e)};this.#i||!n?(this.#r.add(e),Nn(r,i,!1)):i()}}};#o=e=>{this.#e.delete(e);let t=Array.from(this.#e.values());for(let[e,n]of this.#n)t.includes(e)||(An(n.effect),this.#n.delete(e))};ensure(e,t){var n=I,r=cn();if(t&&!this.#t.has(e)&&!this.#n.has(e)){if(r){var i=document.createDocumentFragment(),a=rn();i.append(a),this.#n.set(e,{effect:En(()=>t(a)),fragment:i})}else this.#t.set(e,En(()=>t(this.anchor)))}if(this.#e.set(n,e),r){for(let[t,r]of this.#t)t===e?n.unskip_effect(r):n.skip_effect(r);for(let[t,r]of this.#n)t===e?n.unskip_effect(r.effect):n.skip_effect(r.effect);n.oncommit(this.#a),n.ondiscard(this.#o)}else M&&(this.anchor=N),this.#a(n)}};function Z(e,t,n=!1){var r;M&&(r=N,be());var i=new Ur(e),a=n?T:0;function o(e,t){if(M){var n=Ce(r);if(e!==parseInt(n.substring(1))){var a=Se();ye(a),i.anchor=a,ve(!1),i.ensure(e,t),ve(!0);return}}i.ensure(e,t)}Tn(()=>{var e=!1;t((t,n=0)=>{e=!0,o(n,t)}),e||o(-1,null)},a)}var Wr=Symbol(`NaN`);function Gr(e,t,n){M&&be();var r=new Ur(e),i=!Ke();Tn(()=>{var e=t();e!==e&&(e=Wr),i&&typeof e==`object`&&e&&(e={}),r.ensure(e,n)})}function Kr(e,t){return t}function qr(e,t,n){for(var i=[],a=t.length,o,s=t.length,c=0;c{if(o){if(o.pending.delete(n),o.done.add(n),o.pending.size===0){var t=e.outrogroups;Jr(e,r(o.done)),t.delete(o),t.size===0&&(e.outrogroups=null)}}else--s},!1)}if(s===0){var l=i.length===0&&n!==null&&e.pending.size===0;if(l){var u=n,d=u.parentNode;sn(d),d.append(u),e.items.clear()}Jr(e,t,!l)}else o={pending:new Set(t),done:new Set},(e.outrogroups??=new Set).add(o)}function Jr(e,t,n=!0){var r;if(e.pending.size>0){r=new Set;for(let t of e.pending.values())for(let n of t)r.add(e.items.get(n).e)}for(var i=0;i{var t=i();return e(t)?t:t==null?[]:r(t)}),p,m=new Map,h=!0;function g(e){v.effect.f&16384||(v.pending.delete(e),v.fallback=d,Qr(v,p,c,n,a),d!==null&&(p.length===0?d.f&33554432?(d.f^=ee,ei(d,null,c)):Fn(d):Nn(d,()=>{d=null})))}function _(e){v.pending.delete(e)}var v={effect:Tn(()=>{p=K(f);var e=p.length;let t=!1;M&&Ce(c)===`[!`!=(e===0)&&(c=Se(),ye(c),ve(!1),t=!0);for(var r=new Set,u=I,v=cn(),y=0;ys(c)):(d=En(()=>s(Yr??=rn())),d.f|=ee)),e>r.size&&ke(``,``,``),M&&e>0&&ye(Se()),!h){if(m.set(u,r),v){for(let[e,t]of l)r.has(e)||u.skip_effect(t.e);u.oncommit(g),u.ondiscard(_)}else g(u)}t&&ve(!0),K(f)}),flags:n,items:l,pending:m,outrogroups:null,fallback:d};h=!1,M&&(c=N)}function Zr(e){for(;e!==null&&!(e.f&32);)e=e.next;return e}function Qr(e,t,n,i,a){var o=!!(i&8),s=t.length,c=e.items,l=Zr(e.effect.first),u,d=null,f,p=[],m=[],h,g,_,v;if(o)for(v=0;v0){var E=i&4&&s===0?n:null;if(o){for(v=0;v{if(f!==void 0)for(_ of f)_.nodes?.a?.apply()})}function $r(e,t,n,r,i,a,o,s){var c=o&1?o&16?Bt(n):Vt(n,!1,!1):null,l=o&2?Bt(i):null;return{v:c,i:l,e:En(()=>(a(t,c??n,l??i,s),()=>{e.delete(r)}))}}function ei(e,t,n){if(e.nodes)for(var r=e.nodes.start,i=e.nodes.end,a=t&&!(t.f&33554432)?t.nodes.start:n;r!==null;){var o=on(r);if(a.before(r),r===i)return;r=o}}function ti(e,t,n){t===null?e.effect.first=n:t.next=n,n===null?e.effect.last=t:n.prev=t}function ni(e,t,...n){var r=new Ur(e);Tn(()=>{let e=t()??null;r.ensure(e,e&&(t=>e(t,...n)))},T)}function ri(e,t,n){var r;M&&(r=N,be());var i=new Ur(e);Tn(()=>{var e=t()??null;if(M&&Ce(r)===`[`!=(e!==null)){var a=Se();ye(a),i.anchor=a,ve(!1),i.ensure(e,e&&(t=>n(t,e))),ve(!0)}else i.ensure(e,e&&(t=>n(t,e)))},T)}var ii=[...` +\r\f\xA0\v`];function ai(e,t,n){var r=e==null?``:``+e;if(t&&(r=r?r+` `+t:t),n){for(var i of Object.keys(n))if(n[i])r=r?r+` `+i:i;else if(r.length)for(var a=i.length,o=0;(o=r.indexOf(i,o))>=0;){var s=o+a;(o===0||ii.includes(r[o-1]))&&(s===r.length||ii.includes(r[s]))?r=(o===0?``:r.substring(0,o))+r.substring(s+1):o=s}}return r===``?null:r}function oi(e,t=!1){var n=t?` !important;`:`;`,r=``;for(var i of Object.keys(e)){var a=e[i];a!=null&&a!==``&&(r+=` `+i+`: `+a+n)}return r}function si(e){return e[0]!==`-`||e[1]!==`-`?e.toLowerCase():e}function ci(e,t){if(t){var n=``,r,i;if(Array.isArray(t)?(r=t[0],i=t[1]):r=t,e){e=String(e).replaceAll(/\/\*.*?\*\//g,``).trim();var a=!1,o=0,s=!1,c=[];r&&c.push(...Object.keys(r).map(si)),i&&c.push(...Object.keys(i).map(si));var l=0,u=-1;let t=e.length;for(var d=0;d{t.every(vi)||(`__defaultValue`in e&&pi(e,!1),`__value`in e&&mi(e,e.__value))});t.observe(e,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[`value`]}),_n(()=>{t.disconnect()})}function gi(e,t,n=t){var r=new WeakSet,i=!0;at(e,`change`,t=>{var i=t?`[selected]`:`:checked`,a;if(e.multiple)a=[].map.call(e.querySelectorAll(i),_i);else{var o=e.querySelector(i)??e.querySelector(`option:not([disabled])`);a=o&&_i(o)}n(a),e.__value=a,I!==null&&r.add(I)}),Sn(()=>{var a=t();if(e===document.activeElement){var o=I;if(r.has(o))return}if(mi(e,a,i),i&&a===void 0){var s=e.querySelector(`:checked`);s!==null&&(a=_i(s),n(a))}e.__value=a,i=!1})}function _i(e){return`__value`in e?e.__value:e.value}function vi(e){if(e.target.closest(`selectedcontent`)!==null)return!0;if(e.type===`childList`){var t=[...e.addedNodes,...e.removedNodes];return t.length>0&&t.every(e=>e.nodeName===`SELECTEDCONTENT`)}return!1}var yi=Symbol(`is custom element`),bi=Symbol(`is html`),xi=de?`link`:`LINK`;function Si(e){if(M){var t=!1,n=()=>{if(!t){if(t=!0,e.hasAttribute(`value`)){var n=e.value;Q(e,`value`,null),e.value=n}if(e.hasAttribute(`checked`)){var r=e.checked;Q(e,`checked`,null),e.checked=r}}};e[le]=n,Ye(n),rt()}}function Ci(e,t){var n=wi(e);n.checked!==(n.checked=t??void 0)&&(e.checked=t)}function Q(e,t,n,r){var i=wi(e);M&&(i[t]=e.getAttribute(t),t===`src`||t===`srcset`||t===`href`&&e.nodeName===xi)||i[t]!==(i[t]=n)&&(t===`loading`&&(e[ae]=n),n==null?e.removeAttribute(t):typeof n!=`string`&&Ei(e).has(t)?e[t]=n:e.setAttribute(t,n))}function wi(e){return e[oe]??={[yi]:e.nodeName.includes(`-`),[bi]:e.namespaceURI===pe}}var Ti=new Map;function Ei(e){var t=e.getAttribute(`is`)||e.nodeName,n=Ti.get(t);if(n)return n;Ti.set(t,n=new Set);for(var r,i=e,a=Element.prototype;a!==i;){for(var s in r=o(i),r)r[s].set&&s!==`innerHTML`&&s!==`textContent`&&s!==`innerText`&&n.add(s);i=l(i)}return n}function Di(e,t,n=t){var r=new WeakSet;at(e,`input`,async i=>{var a=i?e.defaultValue:e.value;if(a=Mi(e)?Ni(a):a,n(a),I!==null&&r.add(I),await lr(),a!==(a=t())){var o=e.selectionStart,s=e.selectionEnd,c=e.value.length;if(e.value=a??``,s!==null){var l=e.value.length;o===s&&s===c&&l>c?(e.selectionStart=l,e.selectionEnd=l):(e.selectionStart=o,e.selectionEnd=Math.min(s,l))}}}),(M&&e.defaultValue!==e.value||fr(t)==null&&e.value)&&(n(Mi(e)?Ni(e.value):e.value),I!==null&&r.add(I)),wn(()=>{var n=t();if(e===document.activeElement){var i=I;if(r.has(i))return}Mi(e)&&n===Ni(e.value)||(e.type!==`date`||n||e.value)&&n!==e.value&&(e.value=n??``)})}var Oi=new Set;function ki(e,t,n,r,i=r){var a=n.getAttribute(`type`)===`checkbox`,o=e;let s=!1;if(t!==null)for(var c of t)o=o[c]??=[];o.push(n),at(n,`change`,()=>{var e=n.__value;a&&(e=ji(o,e,n.checked)),i(e)},()=>i(a?[]:null)),wn(()=>{var e=r();M&&n.defaultChecked!==n.checked?s=!0:a?(e||=[],n.checked=e.includes(n.__value)):n.checked=Zt(n.__value,e)}),_n(()=>{var e=o.indexOf(n);e!==-1&&o.splice(e,1)}),Oi.has(o)||(Oi.add(o),Ye(()=>{o.sort((e,t)=>e.compareDocumentPosition(t)===4?-1:1),Oi.delete(o)})),Ye(()=>{if(s){var e=a?ji(o,e,n.checked):o.find(e=>e.checked)?.__value;i(e)}})}function Ai(e,t,n=t){at(e,`change`,t=>{n(t?e.defaultChecked:e.checked)}),(M&&e.defaultChecked!==e.checked||fr(t)==null)&&n(e.checked),wn(()=>{e.checked=!!t()})}function ji(e,t,n){for(var r=new Set,i=0;i{var n=this.#e.get(e);n.delete(t),n.size===0&&(this.#e.delete(e),this.#t.unobserve(e))}}#r(){return this.#t??=new ResizeObserver(t=>{for(var n of t){e.entries.set(n.target,n);for(var r of this.#e.get(n.target)||[])r(n)}})}}({box:`border-box`});function Fi(e,t,n){var r=Pi.observe(e,()=>n(e[t]));Sn(()=>(fr(()=>n(e[t])),r))}function Ii(e,t){return e===t||e?.[k]===t}function Li(e=Ge(),t,n,r){var i=Ve.r,a=G;return Sn(()=>{var o,s;return wn(()=>{o=s,s=r?.()||[],fr(()=>{Ii(n(...s),e)||(t(e,...s),o&&Ii(n(...o),e)&&t(null,...o))})}),()=>{let r=a;for(;r!==i&&r.parent!==null&&r.parent.f&33554432;)r=r.parent;let o=()=>{s&&Ii(n(...s),e)&&t(null,...s)},c=r.teardown;r.teardown=()=>{o(),c?.()}}}),e}function Ri(e,t,n,r,i){var a=()=>{r(n[e])};n.addEventListener(t,a),i?wn(()=>{n[e]=i()}):a(),(n===document.body||n===window||n===document)&&_n(()=>{n.removeEventListener(t,a)})}function zi(e=!1){let t=Ve,n=t.l.u;if(!n)return;let r=()=>pr(t.s);if(e){let e=0,n={},i=ut(()=>{let r=!1,i=t.s;for(let e in i)i[e]!==n[e]&&(n[e]=i[e],r=!0);return r&&e++,e});r=()=>K(i)}n.b.length&&bn(()=>{Bi(t,r),m(n.b)}),vn(()=>{let e=fr(()=>n.m.map(p));return()=>{for(let t of e)typeof t==`function`&&t()}}),n.a.length&&vn(()=>{Bi(t,r),m(n.a)})}function Bi(e,t){if(e.l.s)for(let t of e.l.s)K(t);t()}var Vi=!1;function Hi(e){var t=Vi;try{return Vi=!1,[e(),Vi]}finally{Vi=t}}var Ui={get(e,t){let n=e.props.length;for(;n--;){let r=e.props[n];if(d(r)&&(r=r()),typeof r==`object`&&r&&t in r)return r[t]}},set(e,t,n){let r=e.props.length;for(;r--;){let i=e.props[r];d(i)&&(i=i());let o=a(i,t);if(o&&o.set)return o.set(n),!0}return!1},getOwnPropertyDescriptor(e,t){let n=e.props.length;for(;n--;){let r=e.props[n];if(d(r)&&(r=r()),typeof r==`object`&&r&&t in r){let e=a(r,t);return e&&!e.configurable&&(e.configurable=!0),e}}},has(e,t){if(t===k||t===ie)return!1;for(let n of e.props)if(d(n)&&(n=n()),n!=null&&t in n)return!0;return!1},ownKeys(e){let t=[];for(let n of e.props)if(d(n)&&(n=n()),n){for(let e in n)t.includes(e)||t.push(e);for(let e of Object.getOwnPropertySymbols(n))t.includes(e)||t.push(e)}return t}};function Wi(...e){return new Proxy({props:e},Ui)}function Gi(e,t,n,r){var i=!ze||!!(n&2),o=!!(n&8),s=!!(n&16),c=r,l=!0,u=void 0,d=()=>s&&i?(u??=ut(r),K(u)):(l&&(l=!1,c=s?fr(r):r),c);let f;if(o){var p=k in e||ie in e;f=a(e,t)?.set??(p&&t in e?n=>e[t]=n:void 0)}var m,h=!1;o?[m,h]=Hi(()=>e[t]):m=e[t],m===void 0&&r!==void 0&&(m=d(),f&&(i&&Pe(t),f(m)));var g=i?()=>{var n=e[t];return n===void 0?d():(l=!0,n)}:()=>{var n=e[t];return n!==void 0&&(c=void 0),n===void 0?c:n};if(i&&!(n&4))return g;if(f){var _=e.$$legacy;return(function(e,t){return arguments.length>0?((!i||!t||_||h)&&f(t?g():e),e):g()})}var v=!1,y=(n&1?ut:pt)(()=>(v=!1,g()));o&&K(y);var b=G;return(function(e,t){if(arguments.length>0){let n=t?K(y):i&&o?Yt(e):e;return R(y,n),v=!0,c!==void 0&&(c=n),e}return Bn&&v||b.f&16384?y.v:K(y)})}function Ki(e){Ve===null&&De(`onMount`),ze&&Ve.l!==null?Ji(Ve).m.push(e):vn(()=>{let t=fr(e);if(typeof t==`function`)return t})}function qi(e){Ve===null&&De(`onDestroy`),Ki(()=>()=>fr(e))}function Ji(e){var t=e.l;return t.u??={a:[],b:[],m:[]}}typeof window<`u`&&((window.__svelte??={}).v??=new Set).add(`5`);function Yi(e,t){if(e instanceof RegExp)return{keys:!1,pattern:e};var n,r,i,a,o=[],s=``,c=e.split(`/`);for(c[0]||c.shift();i=c.shift();)n=i[0],n===`*`?(o.push(`wild`),s+=`/(.*)`):n===`:`?(r=i.indexOf(`?`,1),a=i.indexOf(`.`,1),o.push(i.substring(1,~r?r:~a?a:i.length)),s+=~r&&!~a?`(?:/([^/]+?))?`:`/([^/]+?)`,~a&&(s+=(~r?`?`:``)+`\\`+i.substring(a))):s+=`/`+i;return{keys:o,pattern:RegExp(`^`+s+(t?`(?=$|/)`:`/?$`),`i`)}}var Xi=new class{#e=L(Zi());get _loc(){return K(this.#e)}set _loc(e){R(this.#e,e)}#t=F(()=>this._loc.location);get _location(){return K(this.#t)}set _location(e){R(this.#t,e)}#n=F(()=>this._loc.querystring);get _querystring(){return K(this.#n)}set _querystring(e){R(this.#n,e)}#r=L(void 0);get _params(){return K(this.#r)}set _params(e){R(this.#r,e)}get loc(){return this._loc}get location(){return this._location}get querystring(){return this._querystring}get params(){return this._params}constructor(){typeof window<`u`?window.addEventListener(`hashchange`,()=>{this._loc=Zi()}):console.warn(`[svelte-spa-router] window 'window' is not defined, skipping initiation`)}};function Zi(){let e=typeof window<`u`?window.location.href:``,t=e.indexOf(`#/`),n=t>-1?e.substr(t+1):`/`,r=n.indexOf(`?`),i=``;return r>-1&&(i=n.substr(r+1),n=n.substr(0,r)),{location:n,querystring:i}}async function Qi(e){if(!e||e.length<1||e.charAt(0)!=`/`&&e.indexOf(`#/`)!==0)throw Error(`Invalid parameter location`);await lr();let t=(e.charAt(0)==`#`?``:`#`)+e;try{let e={...history.state};delete e.__svelte_spa_router_scrollX,delete e.__svelte_spa_router_scrollY,window.history.replaceState(e,``,t)}catch{console.warn("Caught exception while replacing the current page. If you're running this in the Svelte REPL, please note that the `replace` method might not work in this environment.")}window.dispatchEvent(new Event(`hashchange`))}function $i(e){e?window.scrollTo(e.__svelte_spa_router_scrollX||0,e.__svelte_spa_router_scrollY||0):window.scrollTo(0,0)}function ea(e,t){Ue(t,!0);let n=Gi(t,`routes`,19,()=>({})),r=Gi(t,`prefix`,3,``),i=Gi(t,`restoreScrollState`,3,!1),a=Gi(t,`onConditionsFailed`,3,()=>{}),o=Gi(t,`onRouteLoaded`,3,()=>{}),s=Gi(t,`onRouteLoading`,3,()=>{}),c=Gi(t,`onRouteEvent`,3,()=>{});class l{path;component;conditions;userData;props;_pattern;_keys;constructor(e,t){let n=e=>typeof e==`object`&&!!e&&e._sveltesparouter===!0;if(!t||typeof t!=`function`&&!n(t))throw Error(`Invalid component object`);if(!e||typeof e==`string`&&(e.length<1||e.charAt(0)!=`/`&&e.charAt(0)!=`*`)||typeof e==`object`&&!(e instanceof RegExp))throw Error(`Invalid value for "path" argument - strings must start with / or *`);let r=Yi(e);if(this.path=e,n(t)){let e=t;this.component=e.component,this.conditions=e.conditions||[],this.userData=e.userData,this.props=e.props||{}}else{let e=t;this.component=()=>Promise.resolve(e),this.conditions=[],this.props={}}this._pattern=r.pattern,this._keys=r.keys}match(e){if(r()){if(typeof r()==`string`){if(e.startsWith(r()))e=e.substr(r().length)||`/`;else return null}else if(r()instanceof RegExp){let t=e.match(r());if(t&&t[0])e=e.substr(t[0].length)||`/`;else return null}}let t=this._pattern.exec(e);if(t===null)return null;if(this._keys===!1)return t;let n={},i=0;for(;i{u.push(new l(t,e))}):Object.keys(n()).forEach(e=>{let t=n();u.push(new l(e,t[e]))});let d=L(null),f=L(null),p=L({}),m=null,h=null;vn(()=>{history.scrollRestoration=i()?`manual`:`auto`}),vn(()=>{if(!i())return;let e=e=>{m=e.state&&(e.state.__svelte_spa_router_scrollY||e.state.__svelte_spa_router_scrollX)?e.state:null};return window.addEventListener(`popstate`,e),()=>window.removeEventListener(`popstate`,e)});async function g(e,t){await lr(),e(t)}vn(()=>{let e=Xi.loc,t=!1;return fr(async()=>{let n=0;for(;n{t=!0}});function _(e){return e&&typeof e==`object`&&Object.keys(e).length?e:null}var v=Nr(),y=B(v),b=e=>{let t=F(()=>K(d));var n=Nr(),r=B(n),i=e=>{var n=Nr();ri(B(n),()=>K(t),(e,t)=>{t(e,Wi({get params(){return K(f)},get onRouteEvent(){return c()}},()=>K(p)))}),Y(e,n)},a=e=>{var n=Nr();ri(B(n),()=>K(t),(e,t)=>{t(e,Wi({get onRouteEvent(){return c()}},()=>K(p)))}),Y(e,n)};Z(r,e=>{K(f)?e(i):e(a,-1)}),Y(e,n)};Z(y,e=>{K(d)&&e(b)}),Y(e,v),We()}var ta=[`bars`,`pace`,`pie`,`fuel`,`resets`,`thresholds`];function na(e){return e===`zone`||e===`consumption-pace`?`pace`:typeof e==`string`&&ta.includes(e)?e:`bars`}var ra=`codexometer.web.preferences.v1`,ia={statusLine:null,tab:`quota`,view:`bars`,selected:``,defaultDetail:0,layouts:[]};function aa(){try{let e=JSON.parse(localStorage.getItem(ra)||`null`);return!e||typeof e!=`object`?ia:{statusLine:Array.isArray(e.statusLine)?[...new Set(e.statusLine.filter(e=>typeof e==`string`&&e.length<64))].slice(0,32):null,tab:[`quota`,`sessions`,`usage`].includes(e.tab)?e.tab:`quota`,view:na(e.view),selected:typeof e.selected==`string`?e.selected.slice(0,256):``,defaultDetail:+(e.defaultDetail===1),layouts:Array.isArray(e.layouts)?e.layouts.filter(e=>{if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.id==`string`&&t.id.length<=256&&[0,1,2].includes(t.level)}).slice(-100):[]}}catch{return ia}}var oa=Yt(aa()),sa=Yt({open:!1}),ca=Yt({requests:0});function la(){let e=JSON.stringify(oa);try{localStorage.setItem(ra,e)}catch{}}function ua(){return oa.tab===`quota`?`/quota/`+oa.view:`/`+oa.tab}function da(e){return oa.layouts.find(t=>t.id===e)?.level??oa.defaultDetail}function fa(e){oa.defaultDetail=e,oa.layouts=[]}function pa(e,t){oa.layouts=[...oa.layouts.filter(t=>t.id!==e),{id:e,level:Math.max(0,Math.min(2,t))}].slice(-100)}var $=Yt({data:null,connected:!1,error:``}),ma=e=>e==null?`Unavailable`:e.toLocaleString(`en-GB`),ha=e=>!e||String(e).startsWith(`0001-`)?`Not yet available`:new Date(typeof e==`number`?e*1e3:e).toLocaleString(`en-GB`),ga=`codexometer.web.session`,_a=class extends Error{},va;async function ya(e,t,n){if(!va||!$.connected||!$.data?.control||$.data.sessionsError)throw Error(`Session controls unavailable. Check the live connection.`);return await va(e,t,n)}function ba(){let e=new AbortController,t,n=``;va=async(t,r,i)=>{let a=await fetch(`/api/control/`+t,{method:`POST`,headers:{Authorization:`Bearer ${n}`,"Content-Type":`application/json`},body:JSON.stringify(r),signal:AbortSignal.any([e.signal,AbortSignal.timeout(8e3),...i?[i]:[]]),cache:`no-store`});if(!a.ok)throw a.status===502?Error(`Outcome uncertain. Check Codex before taking another action; nothing was retried.`):new _a(`Action rejected, expired or changed. Refresh and check Codex; nothing was retried.`);return a.json()};try{n=sessionStorage.getItem(ga)||``}catch{}let r=location.hash,i=r.startsWith(`#pair=`)?r.slice(6):``;(i||!r||r===`#`)&&(history.replaceState(null,``,`/#`+ua()),window.dispatchEvent(new HashChangeEvent(`hashchange`)));async function a(){if(i)try{let t=await fetch(`/api/pair`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({secret:i}),signal:e.signal});if(!t.ok)throw Error(`Pairing link expired or already used. Restart --web for a fresh link.`);n=(await t.json()).token;try{sessionStorage.setItem(ga,n)}catch{}}catch(t){e.signal.aborted||($.error=t instanceof Error?t.message:`Pairing failed`);return}n?await o():$.error=`Open the private pairing link printed by codexometer --web in your terminal.`}async function o(){try{let t=await fetch(`/api/events`,{headers:{Authorization:`Bearer ${n}`},signal:e.signal,cache:`no-store`});if(t.status===401){try{sessionStorage.removeItem(ga)}catch{}$.error=`Browser session expired. Restart --web and open its new pairing link.`;return}if(!t.ok||!t.body)throw Error(`Live connection unavailable`);let r=t.body.getReader(),i=new TextDecoder,a=``;for(;!e.signal.aborted;){let{done:e,value:t}=await r.read();if(e)break;a+=i.decode(t,{stream:!0});let n;for(;(n=a.indexOf(` + +`))>=0;){let e=a.slice(0,n);a=a.slice(n+2),e.startsWith(`data: `)&&($.data=JSON.parse(e.slice(6)),$.connected=!0,$.error=``)}}}catch{}finally{$.connected=!1}e.signal.aborted||($.error=`Connection lost — showing last observation. Reconnecting…`,t=setTimeout(o,2500))}return a(),()=>{va=void 0,e.abort(),clearTimeout(t),$.connected=!1}}Be();var xa=J(`

Quota refresh failed. Policy state is based on the last successful + observation.

`),Sa=J(`

`),Ca=J(`

The longest Codex quota window selects one active model profile. ASK + creates a per-session review; AUTO applies the profile on the next + eligible check.

`,1),wa=J(`

No threshold-based model steps were configured at launch.

`);function Ta(e,t){Ue(t,!1),zi();var n=Nr(),r=B(n),i=e=>{var t=Ca(),n=B(t),r=V(n),i=H(n,2),a=e=>{Y(e,xa())};Z(i,e=>{$.data.quotaError&&e(a)});var o=H(i,2),s=z(o),c=V(s),l=H(s,4);Xr(l,5,()=>$.data.thresholds,Kr,(e,t)=>{var n=Sa();let r;var i=z(n),a=V(i),o=H(i,2),s=z(o),c=V(s,!0),l=V(H(s,2));P(o);var u=H(o,2),d=V(u,!0),f=V(H(u,2));P(n),U((e,i)=>{r=li(n,1,``,null,r,{active:K(t).state===`ACTIVE`,next:K(t).state===`NEXT`}),X(a,`${K(t).threshold??``}%`),X(c,K(t).model),X(l,`${e??``} REASONING // ${i??``} + SPEED`),X(d,K(t).mode),X(f,`${K(t).state??``}${K(t).state===`NEXT`?` // ${K(t).remaining||0} PP TO GO`:``}`)},[()=>K(t).effort.toUpperCase(),()=>K(t).speed.toUpperCase()]),Y(e,n)}),P(l),P(o),U(e=>{X(r,`MODEL STEP POLICY // OBSERVED ${e??``}`),X(c,`THRESHOLDS // ${$.data.thresholds.length??``} CONFIGURED`)},[()=>ha($.data.quotaAt)]),Y(e,t)},a=e=>{Y(e,wa())};Z(r,e=>{$.data?.thresholds?.length?e(i):e(a,-1)}),Y(e,n),We()}var Ea=jr(` `,1),Da=jr(` `,1),Oa=jr(` `,1),ka=jr(` `,1),Aa=J(`
`,1),ja=J(` `),Ma=J(`
Quota observations
Observed atPeriod elapsedConsumedTrail segment
`),Na=J(`
OBSERVATION DATA
`),Pa=J(`
CONSUMPTIONQUOTA PERIOD ELAPSED
`,1);function Fa(e,t){let n=Pr();Ue(t,!0);let r=Gi(t,`trail`,19,()=>[]),i=L(!1),a=L(!0),o=L(`window`),s=[0,25,50,75,100],c=F(()=>t.plot?.known?t.plot.elapsed:t.elapsed),l=L(400),u=L(240),d=F(()=>K(l)-24),f=F(()=>K(u)-48),p=F(()=>Math.max(1,K(d)-48)),m=F(()=>Math.max(1,K(f)-20)),h=F(()=>48+Math.max(0,Math.min(100,K(c)))/100*K(p));function g(e){return K(f)-(e-0)/100*K(m)}let _=F(()=>g(t.used)),v=F(()=>t.used-K(c)),y=F(()=>r().map((e,t)=>`${t===0||e.break?`M`:`L`}${48+e.elapsed/100*K(p)} ${g(e.used)}`).join(` `)),b=F(()=>{let e=0;for(let t=r().length-1;t>0;t--)if(r()[t].break){e=t;break}return r().slice(e)}),x=F(()=>K(b).length<2?0:Math.max(0,Date.parse(K(b).at(-1).at)-Date.parse(K(b)[0].at))),S=F(()=>Object.fromEntries((t.plot?.modes||[]).map(e=>[e.id,e.available])));vn(()=>{K(o)!==`window`&&!K(S)[K(o)]&&R(o,`window`)});let C=F(()=>{let e=t.plot?.modes.find(e=>e.id===K(o));if(!e?.segment)return null;let{x1:n,y1:r,x2:i,y2:a}=e.segment,s=Math.hypot((i-n)/100*K(p),(a-r)/100*K(m)),c=Math.max(3,Math.min(9,Math.floor(s/65)+2));return{path:`M${Array.from({length:c},(e,t)=>{let o=n+(i-n)*t/(c-1),s=r+(a-r)*t/(c-1);return`${48+o/100*K(p)} ${g(s)}`}).join(` L`)}`,projected:e.projected,safe:e.safe}}),w=F(()=>{if(K(b).length===0)return`NO OBSERVATIONS`;if(K(b).length===1)return`ONE OBSERVATION`;let e=Math.floor(K(x)/6e4);if(e<60)return`${e} MIN CONTINUOUS`;let t=Math.floor(e/60);return t<24?`${t}H CONTINUOUS`:`${Math.floor(t/24)}D ${t%24}H CONTINUOUS`});var T=Pa(),E=B(T),D=z(E);let ee;var te=z(D),O=z(te),ne=H(O);P(te);var k=H(te),re=H(k);Xr(re,17,()=>s,Kr,(e,t)=>{var n=Ea(),r=B(n),i=H(r),a=V(i);U(()=>{Q(r,`x1`,48+K(t)/100*K(p)),Q(r,`x2`,48+K(t)/100*K(p)),Q(r,`y2`,K(f)),Q(i,`x`,48+K(t)/100*K(p)),Q(i,`y`,K(f)+20),X(a,`${K(t)??``}%`)}),Y(e,n)});var ie=H(re);Xr(ie,17,()=>s,Kr,(e,t)=>{var n=Da(),r=B(n),i=H(r),a=V(i);U((e,n,o)=>{Q(r,`y1`,e),Q(r,`x2`,K(d)),Q(r,`y2`,n),Q(i,`y`,o),X(a,`${K(t)??``}%`)},[()=>g(K(t)),()=>g(K(t)),()=>g(K(t))+4]),Y(e,n)});var ae=H(ie),oe=H(ae),se=H(oe),A=e=>{var t=Oa(),n=B(t),i=H(n),a=H(i),o=V(z(a));P(a),U((e,t)=>{Q(n,`d`,K(y)),Q(i,`d`,K(y)),Q(a,`cx`,48+r()[0].elapsed/100*K(p)),Q(a,`cy`,e),X(o,`First observation: ${t??``}`)},[()=>g(r()[0].used),()=>ha(r()[0].at)]),Y(e,t)};Z(se,e=>{K(a)&&r().length&&e(A)});var ce=H(se),le=e=>{var t=ka(),r=B(t),i=H(r),a=V(z(i));P(i),U(e=>{Q(r,`d`,K(C).path),Q(i,`d`,K(C).path),Q(i,`marker-mid`,`url(#${n}-trend-arrow)`),X(a,`${K(o)===`window`?`Window-average pace`:`Recent consumption trajectory`} projects ${e??``}% at reset if continued`)},[()=>K(C).projected.toFixed(1)]),Y(e,t)};Z(ce,e=>{K(C)&&e(le)});var ue=H(ce,2),de=H(ue),fe=H(de),j=V(z(fe));P(fe),P(D),P(E);var pe=H(E,2),me=z(pe),he=z(me),ge=H(he,3),_e=V(ge,!0),M=H(ge,2),ve=e=>{var t=Aa(),n=V(H(B(t)));U(e=>X(n,`TREND // ${e??``}`),[()=>K(C).projected>=100?`QUOTA EXHAUSTION PROJECTED`:`${K(C).projected.toFixed(1)}% PROJECTED AT RESET`]),Y(e,t)};Z(M,e=>{K(C)&&e(ve)}),P(me);var N=H(me,2),ye=z(N),be=z(ye);Si(be),xe(),P(ye);var Se=H(ye,2),Ce=H(z(Se),2),we=z(Ce);we.value=we.__value=`off`;var Te=H(we),Ee=V(Te);Te.value=Te.__value=`halfHour`;var De=H(Te),Oe=V(De);De.value=De.__value=`hour`;var ke=H(De),Ae=V(ke);ke.value=ke.__value=`day`;var je=H(ke),Me=V(je);je.value=je.__value=`window`,P(Ce),hi(Ce),P(Se);var Ne=V(H(Se,2));P(N),P(pe);var Pe=H(pe,2),Fe=e=>{var t=Na(),n=H(z(t),2),a=e=>{var t=Ma(),n=z(t),i=H(z(n),2);Xr(i,21,r,Kr,(e,t,n)=>{var r=ja(),i=z(r),a=z(i),o=V(a,!0);P(i);var s=H(i),c=V(s),l=H(s),u=V(l),d=V(H(l),!0);P(r),U((e,r)=>{Q(a,`datetime`,K(t).at),X(o,e),X(c,`${r??``}%`),X(u,`${K(t).used??``}%`),X(d,n===0?`First observation`:K(t).break?`Gap before this observation`:`Connected to previous observation`)},[()=>ha(K(t).at),()=>K(t).elapsed.toFixed(1)]),Y(e,r)}),P(i),P(n),P(t),Y(e,t)};Z(n,e=>{K(i)&&e(a)}),P(t),Ri(`open`,`toggle`,t,e=>R(i,e),()=>K(i)),Y(e,t)};Z(Pe,e=>{r().length&&e(Fe)}),U((e,r,i,a,o,s)=>{Q(D,`viewBox`,`0 0 ${K(l)} ${K(u)}`),Q(D,`aria-label`,e),ee=di(D,``,ee,{"--trend-color":K(C)?.safe?`#115b35`:`#7f1825`}),Q(O,`id`,n),Q(ne,`id`,n+`-trend-arrow`),Q(k,`width`,K(p)),Q(k,`height`,K(m)),Q(k,`fill`,`url(#${n})`),Q(ae,`d`,`M48 20 V${K(f)} H${K(d)}`),Q(oe,`y1`,r),Q(oe,`x2`,K(d)),Q(oe,`y2`,i),Q(ue,`x`,48+K(p)/2),Q(ue,`y`,K(u)-5),Q(de,`cx`,K(h)),Q(de,`cy`,K(_)),Q(fe,`cx`,K(h)),Q(fe,`cy`,K(_)),X(j,`${t.used??``}% consumed // ${a??``}% of period elapsed`),X(he,`${t.used??``}% USED // ${o??``}% TIME ELAPSED`),X(_e,s),Te.disabled=!K(S).halfHour,X(Ee,`LAST 30 MINUTES${K(S).halfHour?``:` — UNAVAILABLE`}`),De.disabled=!K(S).hour,X(Oe,`LAST HOUR${K(S).hour?``:` — UNAVAILABLE`}`),ke.disabled=!K(S).day,X(Ae,`LAST 24 HOURS${K(S).day?``:` — UNAVAILABLE`}`),je.disabled=!K(S).window,X(Me,`FROM WINDOW START${K(S).window?``:` — NO TIME ELAPSED`}`),X(Ne,`HISTORY // ${K(w)??``}`)},[()=>`Pace: ${t.used}% consumed, ${K(c).toFixed(1)}% of quota period elapsed. ${K(v)>0?`Above`:K(v)<0?`Below`:`On`} the steady-consumption line.${K(a)&&r().length?` Trace path contains ${r().length} ${r().length===1?`observation`:`observations`}; gaps are not interpolated.`:``}`,()=>g(0),()=>g(100),()=>K(c).toFixed(1),()=>K(c).toFixed(1),()=>Math.abs(K(v))<.05?`ON PACE`:K(v)>0?`ABOVE THE LINE — CONSUMING FASTER THAN TIME`:`BELOW THE LINE — WITHIN PACE`]),Fi(E,`clientWidth`,e=>R(l,e)),Fi(E,`clientHeight`,e=>R(u,e)),Ai(be,()=>K(a),e=>R(a,e)),gi(Ce,()=>K(o),e=>R(o,e)),Y(e,T),We()}var Ia=J(` `),La=J(`

Quota refresh failed. Values below are the last successful observation.

`),Ra=J(`

`),za=J(`

Expiry details unavailable. No listed expiry does not mean no expiry.

`),Ba=J(`

`),Va=J(`

Read-only preview. Use the terminal to redeem a reset.

The backend may return only some credits. This list does not establish + redemption order.

`),Ha=J(` `,1),Ua=jr(``),Wa=jr(``),Ga=J(`
`),Ka=J(`

Cycle duration or reset date unavailable — position cannot be + plotted.

`),qa=J(`
EMPTYFULL
`),Ja=J(`

`,1),Ya=J(`
`,1),Xa=J(`

`),Za=J(`

`),Qa=J(`

No quota windows reported yet.

`),$a=J(`
`,1),eo=J(`

All reported windows are shown. API-equivalent learning and quota status + scoring remain in the terminal for this first preview.

`,1),to=J(` `,1);function no(e,t){Ue(t,!0);let n=Gi(t,`params`,19,()=>({})),r=F(()=>ta.filter(e=>e!==`thresholds`||!!$.data?.thresholds?.length)),i=L(Yt(Date.now())),a=F(()=>K(r).includes(na(n().view))?na(n().view):`bars`);Ki(()=>{let e=setInterval(()=>R(i,Date.now(),!0),1e3);return()=>clearInterval(e)});function o(e){return!e.duration||e.duration<=0||!e.reset?null:Math.max(0,Math.min(100,100*(1-(e.reset*1e3-K(i))/(e.duration*6e4))))}vn(()=>{oa.view=K(a),(n().view===`zone`||n().view===`consumption-pace`)&&Qi(`/quota/pace`)});function s(e){let t=e/100*Math.PI*2;return`M60 60 L60 14 A46 46 0 ${+(e>50)} 1 ${60+46*Math.sin(t)} ${60-46*Math.cos(t)} Z`}var c=to(),l=B(c);Xr(l,21,()=>K(r),Kr,(e,t)=>{var n=Ia();let r;var i=V(n,!0);U(e=>{Q(n,`href`,`#/quota/`+K(t)),Q(n,`aria-current`,K(a)===K(t)?`page`:void 0),r=li(n,1,``,null,r,{active:K(a)===K(t)}),X(i,e)},[()=>K(t)===`fuel`?`FUEL TANK`:K(t).toUpperCase()]),Y(e,n)}),P(l);var u=H(l,2),d=e=>{var t=eo(),n=B(t),r=e=>{Y(e,La())};Z(n,e=>{$.data.quotaError&&e(r)});var i=H(n,2),c=e=>{var t=Ra(),n=V(t);U(()=>X(n,`Last usage accounting issue: + ${$.data.usageDiagnostic??``}`)),Y(e,t)};Z(i,e=>{$.data.usageDiagnostic&&e(c)});var l=H(i,2),u=V(l),d=H(l,2),f=e=>{Ta(e,{})},p=e=>{var t=Va(),n=z(t),r=V(n),i=H(n,4),a=e=>{Y(e,za())};Z(i,e=>{$.data.credits.length||e(a)}),Xr(H(i,2),17,()=>$.data.credits,Kr,(e,t)=>{var n=Ba(),r=z(n),i=V(r),a=V(H(r,2),!0);P(n),U(e=>{X(i,`${(K(t).title||`Quota reset`)??``} // ${K(t).status??``}`),X(a,e)},[()=>K(t).expiryKnown?K(t).expires?`EXPIRES `+ha(K(t).expires):`Does not expire`:`Expiry information unavailable`]),Y(e,n)}),xe(2),P(t),U(()=>X(r,`RESET INVENTORY // ${$.data.creditCount??``} AVAILABLE`)),Y(e,t)},m=e=>{var t=$a(),n=B(t);let r;Xr(n,21,()=>$.data.meters,Kr,(e,t)=>{let n=F(()=>o(K(t)));var r=Za(),i=z(r),c=V(i,!0),l=H(i,2),u=z(l),d=e=>{var n=Ha(),r=B(n),i=V(r),a=V(H(r));U(()=>{X(i,`FREE ${100-K(t).used}%`),X(a,`USED ${K(t).used??``}%`)}),Y(e,n)},f=e=>{var n=Ha(),r=B(n),i=V(r),a=V(H(r));U(()=>{X(i,`USED ${K(t).used??``}%`),X(a,`FREE ${100-K(t).used}%`)}),Y(e,n)};Z(u,e=>{K(a)===`fuel`?e(d):e(f,-1)}),P(l);var p=H(l,2);let m;var h=z(p),g=e=>{var n=Ga(),r=z(n),i=H(z(r)),a=e=>{Y(e,Ua())},o=e=>{var n=Wa();U(e=>Q(n,`d`,e),[()=>s(K(t).used)]),Y(e,n)};Z(i,e=>{K(t).used>=100?e(a):K(t).used>0&&e(o,1)}),P(r),P(n),U(()=>Q(r,`aria-label`,`${K(t).used}% quota used`)),Y(e,n)},_=e=>{var r=Nr(),i=B(r),a=e=>{{let r=F(()=>K(t).trail||[]);Fa(e,{get used(){return K(t).used},get elapsed(){return K(n)},get trail(){return K(r)},get plot(){return K(t).plot}})}},o=e=>{Y(e,Ka())};Z(i,e=>{K(n)===null?e(o,-1):e(a)}),Y(e,r)},v=e=>{var r=Ya(),i=B(r),o=z(i);let s;P(i);var c=H(i,2),l=e=>{Y(e,qa())};Z(c,e=>{K(a)===`fuel`&&e(l)});var u=H(c,2),d=e=>{var t=Ja(),r=B(t),i=V(r),o=H(r,2),s=z(o);let c;P(o),U(e=>{X(i,`RESET CYCLE // ${e??``}% ELAPSED`),c=di(s,``,c,{width:`${K(a)===`fuel`?100-K(n):K(n)}%`})},[()=>Math.floor(K(n))]),Y(e,t)};Z(u,e=>{K(n)!==null&&e(d)}),U(()=>{Q(i,`aria-label`,K(a)===`fuel`?`Fuel remaining`:`Quota used`),Q(i,`aria-valuenow`,K(a)===`fuel`?100-K(t).used:K(t).used),s=di(o,``,s,{width:`${K(a)===`fuel`?100-K(t).used:K(t).used}%`})}),Y(e,r)};Z(h,e=>{K(a)===`pie`?e(g):K(a)===`pace`?e(_,1):e(v,-1)}),P(p);var y=H(p,2),b=V(y),x=H(y,2),S=e=>{var n=Xa(),r=V(n,!0);U(()=>X(r,K(t).details)),Y(e,n)};Z(x,e=>{K(t).details&&e(S)}),P(r),U(e=>{X(c,K(t).name),m=li(p,1,`meter-graphic`,null,m,{"bar-graphic":K(a)===`bars`||K(a)===`fuel`}),X(b,`RESETS // ${e??``}`)},[()=>ha(K(t).reset)]),Y(e,r)}),P(n);var i=H(n,2),c=e=>{Y(e,Qa())};Z(i,e=>{$.data.meters.length||e(c)}),U(()=>r=li(n,1,`quota-grid`,null,r,{radial:K(a)===`pie`,zone:K(a)===`pace`})),Y(e,t)};Z(d,e=>{K(a)===`thresholds`?e(f):K(a)===`resets`?e(p,1):e(m,-1)}),xe(2),U(e=>X(u,`QUOTA // OBSERVED ${e??``}`),[()=>ha($.data.quotaAt)]),Y(e,t)};Z(u,e=>{$.data&&e(d)}),Y(e,c),We()}var ro=J(`
`),io=J(`

`,1);function ao(e,t){Ue(t,!0);let n=Gi(t,`values`,19,()=>[]),r=Gi(t,`label`,3,`Token activity`),i=Gi(t,`capacity`,3,0),a=F(()=>Math.max(0,...n())),o=F(()=>i()>n().length?[...Array(i()-n().length).fill(0),...n()]:n());var s=io(),c=B(s),l=V(c),u=H(c,2);Xr(u,21,()=>K(o),Kr,(e,t)=>{var n=ro();let r;U((e,t)=>{Q(n,`title`,e),r=di(n,``,r,{height:t})},[()=>K(t).toLocaleString(`en-GB`)+` tokens`,()=>`${100*K(t)/Math.max(1,K(a))}%`]),Y(e,n)}),P(u),U((e,t)=>{X(l,`SCALE // 0 — ${e??``} TOKENS`),Q(u,`aria-label`,t)},[()=>K(a).toLocaleString(`en-GB`),()=>`${r()}. Peak ${K(a).toLocaleString(`en-GB`)} tokens.`]),Y(e,s),We()}function oo(e){if(!e||![`LAST REPLY`,`LAST ACTIVITY`].includes(e.contextKind))return``;let t=e.text||e.contextKind===`LAST ACTIVITY`&&e.workingCommand||``;return t.trim()?t:``}var so=[{id:`local-statusline`,label:`/statusline`,help:`Choose fields for Codexometer’s detail footer. Saved display preferences only.`,next:`statusline`},{id:`local-usage`,label:`/usage`,help:`Open Codexometer’s usage history. Existing quota and reset views remain available.`,next:`usage`},{id:`local-copy`,label:`/copy`,help:`Copy the selected session’s observed reply or activity using Codexometer’s copy control.`,next:`copy`},{id:`local-theme`,label:`/theme`,help:`Choose a Codexometer display theme. Saved local display preferences only.`,next:`theme`},{id:`local-exit`,label:`/exit`,help:`Leave this Codexometer interface. Codex sessions continue running.`,next:`exit`},{id:`local-quit`,label:`/quit`,help:`Alias for /exit. Codex sessions continue running.`,next:`quit`}],co=[`surface`,`model`,`feature`,`task start`],lo=J(` `),uo=J(``),fo=J(`

`),po=J(`

`),mo=J(`
`),ho=J(`

`),go=J(`

`,1),_o=J(`

`),vo=J(`

Loading / sending…

`),yo=J(`
`),bo=J(`

`),xo=J(`

 
`),So=J(`
`),Co=J(`
`),wo=J(`

Review expired. Press Enter to review again.

`),To=J(`

`,1),Eo=J(`

`,1),Do=J(`

No available matching commands.

`),Oo=J(` `,1),ko=J(`

`),Ao=J(`

Review expired. Review the option again.

`),jo=J(``),Mo=J(``),No=J(``),Po=J(``),Fo=J(``),Io=J(``),Lo=J(`
`),Ro=J(`
`);function zo(e,t){let n=Pr();Ue(t,!0);let r=e=>{var t=Nr();Xr(B(t),19,()=>K(fe),e=>e.id,(e,t,r)=>{var i=uo();let o;var s=z(i),c=V(s,!0),l=H(s),u=V(l),d=H(l),f=e=>{var n=lo(),r=V(n,!0);U(()=>X(r,K(t).help)),Y(e,n)};Z(d,e=>{(K(v)?.path===``||K(A))&&e(f)}),P(i),U((e,s)=>{Q(i,`id`,`${n}-${K(r)}`),Q(i,`role`,K(A)?`option`:void 0),Q(i,`aria-selected`,K(A)?K(T)===K(r):void 0),Q(i,`tabindex`,K(A)?-1:void 0),i.disabled=K(g)||K(he)&&!a(),Q(i,`title`,K(t).help),Q(i,`aria-label`,e),o=li(i,1,`svelte-7bfwr9`,null,o,{suggested:K(T)===K(r)}),X(c,K(t).label),X(u,`${K(A)&&K(t).selected?` // CURRENT`:``}${s??``}`)},[()=>K(t).label+(K(A)&&K(t).selected?` // CURRENT`:``)+ie(K(t)),()=>ie(K(t))]),xr(`focus`,i,()=>{K(A)||_e(K(r))}),q(`click`,i,()=>{_e(K(r)),ye(K(t))}),Y(e,i)}),Y(e,t)},i=Gi(t,`query`,3,``),a=Gi(t,`localOnly`,3,!1),o=Gi(t,`oncopy`,3,()=>{}),s=Gi(t,`onchange`,3,()=>{}),c=Gi(t,`oncomplete`,3,e=>{}),l=so.map(e=>e.next);function u(){return so.map(e=>[`exit`,`quit`].includes(e.next||``)?{...e,help:`Return to Sessions. The local server and Codex sessions remain running.`}:{...e})}function d(){return{title:`/ COMMANDS`,help:`Local Codexometer commands remain available without session control.`,path:``,revision:``,choices:u()}}function f(){R(b,``),R(O,``),R(x,0),R(ne,[],!0)}function p(){w++,R(g,!1),R(h,!1),R(E,i()),f(),R(_,``),K(ue)?s()():c()(i())}async function m(e){if(!l.includes(e))return!1;if(e===`copy`&&!oo($.data?.sessions.find(e=>e.id===t.session)))return R(_,`No reply or activity text is available to copy for this session.`),!0;if(e===`copy`){let e=o()();p(),await e}else p(),e===`usage`?location.hash=`/usage`:e===`theme`?ca.requests++:e===`statusline`?sa.open=!0:location.hash=`/sessions`;return!0}let h=L(!1),g=L(!1),_=L(``),v=L(null),y=L(null),b=L(``),x=L(0),S=L(Yt(Date.now())),C=L(``),w=0,T=L(0),E=L(null),D=L(void 0),ee=L(void 0),te=L(void 0),O=L(``),ne=L(Yt([])),k=F(()=>!!K(v)?.inputReview),re=F(()=>!!K(y)&&!K(k)||!!K(O)||K(ne).length>0);function ie(e){return e.unavailable?e.unavailable.startsWith(`Waiting for idle.`)?` // WAITING FOR IDLE`:` // UNAVAILABLE`:e.next?` →`:e.action?``:` // HELP`}async function ae(e){K(v)&&!(K(ge)&&K(de))&&R(ne,[...K(ne),{path:K(v).path,index:K(T),input:K(C)}],!0),await N(e)}async function oe(){if(!K(re))return;if(w++,R(g,!1),R(b,``),R(x,0),R(_,``),K(O)){R(O,``),K(D)?.focus();return}if(K(y)&&!K(k)){R(y,null),await lr(),K(D)?.focus();return}let e=K(ne).at(-1);e&&(R(ne,K(ne).slice(0,-1),!0),await N(e.path,e))}function se(){K(v)&&K(C).trim()&&!K(g)&&!K(he)&&ae(K(v).path+`/`+encodeURIComponent(K(C).trim()))}let A=F(()=>K(h)&&!!K(v)?.picker&&!K(y)),ce=F(()=>K(h)&&!K(v)?.input&&!K(y)),le=setInterval(()=>{R(S,Date.now(),!0)},1e3);qi(()=>{clearInterval(le),w++});let ue=F(()=>i().trim().startsWith(`/`)&&!i().trim().startsWith(`//`)),de=F(()=>K(v)?.path===``&&K(ue)?i().trim().slice(1).toLowerCase():``),fe=F(()=>(K(v)?.choices||[]).filter(e=>!K(de)||e.label.toLowerCase().startsWith(`/`+K(de)))),j=F(()=>K(y)||K(fe)[K(T)]),pe=F(()=>!!K(b)&&K(S)K(fe).find(e=>e.id===K(O))),he=F(()=>!$.connected||!!$.data?.sessionsError),ge=F(()=>K(h)&&K(ue)&&(!K(v)||K(v).path===``)&&!K(y));vn(()=>{i(),R(T,0)}),vn(()=>{let e=K(T);K(fe),K(ce)&&lr().then(()=>{let t=K(D)?.children[e];if(!t||!K(D))return;let n=K(D).getBoundingClientRect(),r=t.getBoundingClientRect();r.topn.bottom&&(K(D).scrollTop+=r.bottom-n.bottom)})});function _e(e){K(T)!==e&&K(v)?.picker&&(R(O,``),R(b,``),R(x,0),R(_,``),w++),R(T,e,!0)}function M(){w++,R(g,!1),R(h,!1),R(E,i()),f(),c()(i())}function ve(e){let t=!!K(ee)?.contains(e.target);if(!K(h)||!K(ce)&&!K(y)&&!K(v)?.input||!K(ue)&&!t||K(he)&&!a()||e.isComposing||e.ctrlKey||e.metaKey||e.altKey)return;if(K(v)?.input){e.key===`Escape`&&(e.preventDefault(),e.stopPropagation(),M());return}let n=e.key===`ArrowLeft`,r=e.key.toLowerCase()===`f`,o=e.key.toLowerCase()===`c`;if((o||r||n)&&!t||![`ArrowUp`,`ArrowDown`,`Tab`,`Enter`,`Escape`].includes(e.key)&&!o&&!r&&!n||e.key===`Tab`&&(K(y)||K(v)?.path!==``||e.shiftKey)||e.key===`Enter`&&e.target instanceof Element&&e.target.closest(`button`)&&!K(D)?.contains(e.target)&&!e.target.closest(`[data-command-confirm]`)||K(y)&&!t||K(y)&&[`ArrowUp`,`ArrowDown`].includes(e.key))return;if(e.preventDefault(),e.stopPropagation(),e.key===`Escape`){M();return}if(K(g)||e.repeat&&(o||e.key===`Enter`))return;if(n){K(re)&&oe();return}if(r){N(K(v)?.path||``,void 0,!0);return}if(o){(K(y)?.action||K(A)&&K(O))&&xe();return}if(K(y)){e.key===`Enter`&&K(y).action&&!K(pe)&&!K(_)&&be(K(y));return}if(e.key===`ArrowUp`||e.key===`ArrowDown`){_e(Math.min(Math.max(K(T)+(e.key===`ArrowUp`?-1:1),0),Math.max(K(fe).length-1,0))),!K(A)&&K(D)?.contains(e.target)&&lr().then(()=>(K(D)?.children[K(T)])?.focus());return}if(e.key===`Enter`&&(K(v)?.path===``||K(v)?.path===`goal`)&&/^\/goal\s+\S/.test(i().trim())){let e=K(v)?.path===`goal`?K(fe).find(e=>i().trim().slice(6).trim().toLowerCase()===e.label.toLowerCase()):void 0;if(e){ye(e);return}ae(i().trim().slice(1));return}let s=K(fe)[K(T)];s&&(e.key===`Tab`?c()(s.label):K(A)?be(s):ye(s))}vn(()=>{K(he)&&!a()&&(w++,R(g,!1),f(),K(h)&&(R(h,!1),R(E,i())),R(v,null),R(y,null))}),vn(()=>{K(ue)&&!K(h)&&i()!==K(E)&&(!K(he)||a())&&(f(),R(h,!0),N(``))});async function N(e,n,r=!1){let i=++w;if(K(v)&&R(v,{...K(v),path:e},!0),n&&R(C,n.input,!0),R(g,!0),R(y,null),R(O,``),R(b,``),R(x,0),R(_,``),a())R(v,d(),!0),R(T,n?.index??0,!0),R(g,!1);else try{let a=await ya(`commands`,{session:t.session,command:{mode:`list`,path:e,...r?{refresh:!0}:{}}});i===w&&(a.choices||=[],(e===``||e===`help`)&&(a.choices=[...a.choices.filter(e=>!l.includes(e.label.replace(/^\//,``))),...u()]),R(v,a,!0),R(T,n?Math.min(n.index,Math.max(a.choices.length-1,0)):a.picker?Math.max(a.choices.findIndex(e=>e.selected),0):0,!0),a.picker&&lr().then(()=>K(D)?.focus()),R(C,n?.input??a.value??``,!0),n&&lr().then(()=>{a.input?K(ee)?.querySelector(`input`)?.focus():K(D)?.focus()}),a.inputReview&&a.choices.length===1&&(R(g,!1),ye(a.choices[0])))}catch{i===w&&(R(_,`Command catalogue unavailable. Use Codex.`),e===``||e===`help`?(R(v,d(),!0),R(_,``)):R(v,{title:`/`+e,help:``,path:e,revision:``,choices:[]},!0))}finally{i===w&&R(g,!1)}}async function ye(e){e.unavailable||K(g)||e.next&&await m(e.next)||K(_)||(K(v)?.picker?(_e(K(fe).findIndex(t=>t.id===e.id)),e.action&&(e.reversible||$.data?.noConfirm)?await be(e):K(D)?.focus()):e.next?await ae(e.next):await be(e))}async function be(e){if(K(g)||K(he)||!K(v)||e.unavailable||K(_))return;let n=e.action&&(e.reversible||$.data?.noConfirm);if(K(v).picker){if(K(pe)&&K(O)===e.id)return;R(O,e.id,!0)}else n||(R(y,e,!0),lr().then(()=>K(te)?.focus()));if(R(b,``),R(x,0),R(_,``),!e.action)return;let r=++w;R(g,!0);try{let n=await ya(`commands`,{session:t.session,command:{mode:`prepare`,path:K(v).path,revision:K(v).revision,choice:e.id}});if(r===w){let e=Date.parse(n.expires);if(!n.confirmation||!Number.isFinite(e)||Date.now()>=e)throw Error(`Review confirmation unavailable`);R(b,n.confirmation,!0),R(x,e,!0)}}catch{r===w&&R(_,`Option changed or unavailable. Refresh before retrying.`)}finally{r===w&&R(g,!1)}n&&r===w&&K(b)&&await xe()}async function xe(){if(!K(b)||K(he)||K(g)||Date.now()>=K(x)||!K(v)||K(v).picker&&K(O)!==K(fe)[K(T)]?.id)return;let e=K(b);R(b,``),R(g,!0);let n=++w;try{let r=await ya(`commands`,{session:t.session,confirmation:e,command:{mode:`commit`,path:K(v).path}});n===w&&(R(_,r.message,!0),R(h,!1),R(v,null),R(y,null),f(),R(E,null),s()())}catch{n===w&&R(_,`Change unconfirmed. Check Codex before retrying.`)}finally{n===w&&R(g,!1)}}var Se={handleKey:ve},Ce=Ro(),we=z(Ce),Te=H(we,2),Ee=e=>{var t=fo(),n=V(t,!0);U(()=>X(n,K(_))),Y(e,t)};Z(Te,e=>{K(_)&&!K(h)&&e(Ee)});var De=H(Te,2),Oe=e=>{var i=Lo();let o;var s=z(i),c=e=>{var t=go(),n=B(t),r=V(n,!0),i=H(n,2),a=e=>{var t=mo();Xr(t,21,()=>K(v).help.split(` +`),Kr,(e,t)=>{var n=po(),r=V(n,!0);U(()=>{Q(n,`title`,K(t)),X(r,K(t))}),Y(e,n)}),P(t),Y(e,t)},o=e=>{var t=ho(),n=V(t,!0);U(()=>X(n,K(v)?.help||`Loading live options…`)),Y(e,t)};Z(i,e=>{K(v)?.path===`goal`?e(a):e(o,-1)}),U(()=>X(r,K(v)?.title||`/ COMMANDS`)),Y(e,t)};Z(s,e=>{K(ge)||e(c)});var l=H(s,2),u=e=>{var t=_o(),n=V(t,!0);U(()=>X(n,K(_))),Y(e,t)};Z(l,e=>{K(_)&&e(u)});var d=H(l,2),f=e=>{Y(e,vo())};Z(d,e=>{K(g)&&e(f)});var p=H(d,2),m=e=>{var t=yo(),r=z(t),i=z(r),o=H(i);Si(o),P(r),P(t),U(e=>{Q(t,`id`,`${n}-input`),X(i,`${e??``} `),Q(o,`aria-label`,K(v).inputLabel||`Session name`),Q(o,`maxlength`,K(v).inputLimit||512),o.disabled=K(g)||K(he)&&!a()},[()=>(K(v).inputLabel||`Session name`).toUpperCase()]),xr(`submit`,t,e=>{e.preventDefault(),se()}),Di(o,()=>K(C),e=>R(C,e)),Y(e,t)},h=e=>{var n=xo(),r=z(n),i=V(r,!0),a=H(r,2),o=V(a,!0),s=H(a,2),c=e=>{var n=bo(),r=z(n),i=H(r),a=e=>{Y(e,Mr(`Only this session's saved + name changes.`))},o=F(()=>K(v)?.path.startsWith(`rename/`)),s=e=>{Y(e,Mr(`Only this session’s + directory changes; project configuration is not reloaded.`))},c=F(()=>K(v)?.path.startsWith(`cd/`)),l=e=>{Y(e,Mr(`Only this session's goal + changes.`))},u=F(()=>K(v)?.path.startsWith(`goal/`)),d=e=>{Y(e,Mr(`No change sent yet. Model, reasoning and speed settings can + change token usage and cost. Changes affect subsequent turns of + this session, not global defaults. Automatic quota thresholds + may later supersede model settings.`))};Z(i,e=>{K(o)?e(a):K(c)?e(s,1):K(u)?e(l,2):e(d,-1)}),P(n),U(()=>X(r,`TARGET // ${t.session??``}. `)),Y(e,n)};Z(s,e=>{K(y).action&&e(c)}),P(n),Li(n,e=>R(te,e),()=>K(te)),U(()=>{X(i,K(y).label),X(o,K(y).help||`No additional help supplied by Codex.`)}),Y(e,n)},w=e=>{var t=Oo(),i=B(t),a=e=>{var t=So(),i=z(t);r(i),P(t),Li(t,e=>R(D,e),()=>K(D)),U(()=>Q(t,`aria-activedescendant`,`${n}-${K(T)}`)),Y(e,t)},o=e=>{var t=Co();let n;var i=z(t);r(i),P(t),Li(t,e=>R(D,e),()=>K(D)),U(()=>n=li(t,1,`command-options svelte-7bfwr9`,null,n,{vertical:K(ge)})),Y(e,t)};Z(i,e=>{K(A)?e(a):e(o,-1)});var s=H(i,2),c=e=>{var t=Eo(),n=B(t),r=V(n,!0),i=H(n,2),a=e=>{var t=To(),n=B(t),r=V(n),i=H(n,2),a=e=>{Y(e,wo())};Z(i,e=>{K(b)&&K(S)>=K(x)&&e(a)}),U(()=>X(r,`REVIEW // ${K(me).label??``}. No change sent yet.`)),Y(e,t)};Z(i,e=>{K(me)&&e(a)}),U(()=>X(r,K(fe)[K(T)]?.help)),Y(e,t)};Z(s,e=>{K(A)&&e(c)});var l=H(s,2),u=e=>{Y(e,Do())};Z(l,e=>{!K(g)&&!K(fe).length&&e(u)}),Y(e,t)};Z(p,e=>{K(v)?.input?e(m):K(y)?e(h,1):e(w,-1)});var E=H(p,2),O=e=>{var t=ko(),n=V(t,!0);U(()=>X(n,K(j).unavailable)),Y(e,t)};Z(E,e=>{K(j)?.unavailable&&e(O)});var ne=H(E,2),k=e=>{Y(e,Ao())};Z(ne,e=>{K(b)&&K(S)>=K(x)&&!K(A)&&e(k)});var ie=H(ne,2),ae=z(ie),ce=e=>{var t=Nr(),r=B(t),i=e=>{var t=jo(),r=V(t,!0);U(e=>{Q(t,`form`,`${n}-input`),t.disabled=e,X(r,K(v).reversibleInput||$.data?.noConfirm?`ENTER APPLY`:`ENTER REVIEW`)},[()=>K(g)||K(he)||!K(C).trim()]),Y(e,t)},o=e=>{var t=Nr(),n=B(t),r=e=>{var t=Mo();U(()=>t.disabled=K(g)||K(he)&&!a()),q(`click`,t,xe),Y(e,t)},i=e=>{var t=No(),n=V(t,!0);U(()=>{t.disabled=K(g)||K(he)&&!a(),X(n,K(j).reversible||$.data?.noConfirm?`ENTER APPLY`:`ENTER REVIEW`)}),q(`click`,t,()=>{K(j)&&be(K(j))}),Y(e,t)};Z(n,e=>{K(pe)&&!K(j).reversible&&!$.data?.noConfirm?e(r):e(i,-1)}),Y(e,t)},s=e=>{var t=No(),n=V(t);U(()=>{t.disabled=K(g)||K(he)&&!a(),X(n,`ENTER ${K(j).next?`OPEN`:`HELP`}`)}),q(`click`,t,()=>{K(j)&&ye(K(j))}),Y(e,t)};Z(r,e=>{K(v)?.input?e(i):K(j)?.action&&!K(j).unavailable?e(o,1):!K(y)&&K(j)&&!K(j).unavailable&&e(s,2)}),Y(e,t)};Z(ae,e=>{K(_)||e(ce)});var le=H(ae,2),ue=e=>{var t=Po();U(()=>t.disabled=K(g)),q(`click`,t,oe),Y(e,t)};Z(le,e=>{K(re)&&e(ue)});var de=H(le,2),_e=e=>{var t=Fo();U(()=>t.disabled=K(g)),q(`click`,t,()=>N(K(v)?.path||``,void 0,!0)),Y(e,t)};Z(de,e=>{K(ge)||e(_e)});var Se=H(de,2);P(ie);var Ce=H(ie,2),we=e=>{var t=Io(),n=V(t);U(()=>X(n,`↑/↓ select${K(ge)?` · Tab complete`:``}`)),Y(e,t)};Z(Ce,e=>{!K(v)?.input&&!K(y)&&e(we)}),P(i),Li(i,e=>R(ee,e),()=>K(ee)),U(()=>{Q(i,`aria-label`,K(ge)?`Slash command suggestions`:`Session command options`),o=li(i,1,`svelte-7bfwr9`,null,o,{"command-popup":K(ge)})}),q(`keydown`,i,ve),q(`click`,Se,M),Y(e,i)};return Z(De,e=>{K(h)&&e(Oe)}),P(Ce),U(()=>we.disabled=K(he)&&!a()||K(g)),q(`click`,we,()=>{R(h,!K(h)),R(E,K(h)?null:i(),!0),R(b,``),K(h)?(R(ne,[],!0),N(``)):M()}),Y(e,Ce),We(Se)}Sr([`click`,`keydown`]);var Bo=J(` `,1),Vo=J(`
`),Ho=J(`
/
`);function Uo(e,t){Ue(t,!0);let n=F(()=>t.lines.filter(e=>e.kind===`addition`).length),r=F(()=>t.lines.filter(e=>e.kind===`removal`).length);var i=Ho(),a=z(i),o=z(a),s=V(o),c=V(H(o,2));P(a),Xr(H(a,2),17,()=>t.lines,Kr,(e,t)=>{var n=Vo();let r;var i=z(n),a=e=>{var n=Bo(),r=B(n),i=V(r,!0),a=V(H(r),!0);U(()=>{X(i,K(t).old||``),X(a,K(t).new||``)}),Y(e,n)};Z(i,e=>{K(t).kind!==`heading`&&K(t).kind!==`metadata`&&e(a)});var o=V(H(i),!0);P(n),U(()=>{r=li(n,1,`diff-line svelte-x4gtb6`,null,r,{added:K(t).kind===`addition`,removed:K(t).kind===`removal`,heading:K(t).kind===`heading`}),X(o,K(t).text)}),Y(e,n)}),P(i),U(()=>{Q(a,`aria-label`,`${K(n)} lines added, ${K(r)} lines removed`),X(s,`+${K(n)??``}`),X(c,`−${K(r)??``}`)}),Y(e,i),We()}var Wo=J(`

`),Go=J(`

Send this saved prompt now? This starts Codex work and consumes quota.

`,1),Ko=J(``),qo=J(`

 

In memory only. Follows session model, reasoning and speed. Closing + Codexometer cancels pending work.

`),Jo=J(`

CURRENT PROFILE

MODEL / REASONING LEVEL / SPEED

 

PROPOSED PROFILE

MODEL / REASONING LEVEL / SPEED

 

Applied settings remain after Codexometer closes.

`,1),Yo=J(`

`),Xo=J(`

 
`,1),Zo=J(`

Command unavailable from this observation. Open Codex to inspect the + request.

`),Qo=J(`

Session controls temporarily unavailable. Check Codex for current state.

`),$o=J(`

Checking session controls…

`),es=J(`

`),ts=J(`
About browser controls

Controls require a supported live request from a connected shared + app-server session. Local observations alone cannot provide them.

`),ns=J(` `,1),rs=J(` `),is=J(`Grants permission beyond this one action. Check the scope + carefully.`),as=J(``),os=J(``),ss=J(``),cs=J(``),ls=J(`

Not before this time; when quota and idle allow.

`),us=J(`

`,1),ds=J(`

The existing trigger remains active until saved or deleted. If + it changes or sends while you edit, saving is blocked.

`),fs=J(` `,1),ps=J(``),ms=J(``),hs=J(``),gs=J(` `,1),_s=J(`
  • `),vs=J(`
    View fixed choices

    Type one of these choices exactly. Your answer stays masked.

      `),ys=J(` `,1),bs=J(`

      `),xs=J(`

      `,1),Ss=J(` `,1),Cs=J(``),ws=J(`

      `,1),Ts=J(`

      `);function Es(e,t){Ue(t,!0);let n=[],r=Gi(t,`observedCommand`,3,``),i=Gi(t,`observedFileChanges`,19,()=>[]),a=Gi(t,`review`,3,``),o=Gi(t,`suspended`,3,!1),s=Gi(t,`openScheduler`,3,!1),c=Gi(t,`onProtectedChange`,3,e=>{}),l=Gi(t,`oncopy`,3,()=>{}),u=L(null),d=L(Yt([])),f=L(null),p=L(``),m=L(0),h=L(Yt(Date.now())),g=L(!1),_=L(``),v=L(!1),y=L(!1),b=L(``),x=L(!1),S=L(``),C=L(!1),w=L(``),T=L(`now`),E=L(60),D=L(``),ee=L(!1),te=L(``),O=L(Yt([])),ne=F(()=>K(O).find(e=>e.status!==`sent`)),k=L(void 0),re=L(void 0);function ie(e){if(K(x)){lr().then(()=>K(k)?.focus());return}if(e.status!==`pending`||K(g)||!K(u)?.id||K(u).kind!==`prompt`||K(u).questions?.length)return;R(x,!0),R(S,e.id,!0),lr().then(()=>K(k)?.focus()),R(d,[e.text],!0),R(T,e.trigger===`quota`?`quota`:`at`,!0),R(p,``),R(w,``);let t=new Date(e.at||0);e.trigger!==`quota`&&R(D,new Date(t.getTime()-t.getTimezoneOffset()*6e4).toISOString().slice(0,16),!0)}vn(()=>{s()&&K(ne)&&!K(C)&&K(u)?.id&&K(u).kind===`prompt`&&!K(u).questions?.length&&(R(C,!0),ie(K(ne)))});async function ae(){if(!a())try{R(O,await ya(`schedules`,{session:t.session},M.signal),!0),K(x)&&!K(O).some(e=>e.id===K(S)&&e.status===`pending`)&&R(p,``),K(w)&&!K(O).some(e=>e.id===K(w)&&e.status===`pending`)&&(R(p,``),R(w,``))}catch{}}async function oe(e){try{R(O,await ya(`schedules`,{session:t.session,cancelId:e},M.signal),!0),R(x,!1),R(p,``),R(w,``),R(d,[],!0)}catch(e){R(_,e instanceof Error?e.message:`Unable to cancel trigger.`,!0)}}vn(()=>(c()(K(g)||K(d).some(e=>e.length>0)),()=>c()(!1)));let se=F(()=>$.data?.sessions.find(e=>e.id===t.session)?.status),A=F(()=>!$.connected||!!$.data?.sessionsError),ce=F(()=>a()!==`profile`&&K(u)?.kind===`prompt`&&!K(u).questions?.length&&!!$.data?.profiles?.some(e=>e.session===t.session&&e.pending)),le=F(()=>!K(A)&&!K(y)&&!!K(u)?.id&&K(u).kind===`approval`&&K(b)!==K(u).id),ue=F(()=>K(le)?K(u).command:r()),de=F(()=>!!K(p)&&K(h)K(u)?.questions?.length?K(u).questions:[{text:`Follow-up message`,secret:!1,freeText:!0,options:[]}]),j=F(()=>K(u)?.kind===`approval`||K(u)?.kind===`profile`?K(f)!==null:K(d).length===K(fe).length&&K(d).every((e,t)=>e.trim().length>0&&(K(fe)[t].freeText||K(fe)[t].options?.includes(e)))),pe=F(()=>K(u)?.kind===`prompt`&&!K(u).questions?.length&&K(T)!==`now`),me=F(()=>K(T)===`delay`?K(h)+K(E)*6e4:Date.parse(K(D))),he=F(()=>{if(!K(pe))return``;if(K(x)&&!K(O).some(e=>e.id===K(S)&&e.status===`pending`))return`Trigger changed, removed or already sent. Go back and reopen it; your draft has not been saved.`;if(!K(d)[0]?.trim())return`Enter a message to schedule.`;if(K(T)===`quota`)return``;if(!Number.isFinite(K(me))||K(me)<=K(h))return`Choose a future date/time or a delay greater than zero.`;let e=new Date(K(h));return e.setFullYear(e.getFullYear()+1),K(me)>=e.getTime()?`Choose a time within one year.`:``});function ge(e){return e===void 0?`Not yet available`:new Date(e).toLocaleString([],{timeZoneName:`short`})}function _e(e){if(e.status!==`pending`)return e.status;if(e.trigger===`at`&&e.at&&Date.parse(e.at)>K(h))return`Due in `+Math.ceil((Date.parse(e.at)-K(h))/6e4)+` min`;let t=K(A)?`waiting for a fresh connection`:e.waitingReason||(e.canSend?`ready; awaiting dispatch`:`waiting for quota and an eligible idle session`);return(e.trigger===`at`?`Time reached — `:``)+t}vn(()=>{(K(A)||o()||K(ce)||K(h)>=K(m))&&R(p,``)});let M=new AbortController;Ki(()=>{let e=e=>{e.ctrlKey&&e.key.toLowerCase()===`s`&&!a()&&K(ne)&&(e.preventDefault(),ie(K(ne)))};window.addEventListener(`keydown`,e);let n,r=setInterval(()=>{R(h,Date.now(),!0)},1e3);async function i(){await ae();try{let e=await ya(`offer`,{session:t.session,...a()?{review:a()}:{}},M.signal);if(M.signal.aborted)return;R(y,!1),K(u)?.id!==e.id&&(R(p,``),R(f,null),R(d,Array(Math.max(1,e.questions?.length||0)).fill(``),!0)),R(u,e,!0),K(v)&&e.id&&e.id!==K(b)&&(R(_,``),R(v,!1))}catch{M.signal.aborted||(R(u,null),R(y,!0),R(p,``))}finally{M.signal.aborted||(n=setTimeout(i,2e3))}}return i(),()=>{window.removeEventListener(`keydown`,e),M.abort(),clearTimeout(n),clearInterval(r)}});async function ve(e){if(!e&&K(u)?.kind===`prompt`&&!K(u).questions?.length&&K(d)[0]?.trim().startsWith(`/`)&&!K(d)[0]?.trim().startsWith(`//`)){R(_,`Choose a slash command from COMMANDS; use // for literal slash text.`);return}if(!K(u)?.id||K(g)||K(A)||o()||K(ce)||(e?!e.canSend:!K(j)||K(he)))return;let n=K(u).id;R(g,!0),R(_,``),R(v,!1),R(p,``),R(w,e?.id||``,!0);try{let r=!e&&K(u).kind===`prompt`&&!K(u).questions?.length&&K(T)!==`now`?{trigger:K(T)===`quota`?`quota`:`at`,zone:Intl.DateTimeFormat().resolvedOptions().timeZone,...K(T)===`quota`?{}:{at:(K(T)===`delay`?new Date(Date.now()+K(E)*6e4):new Date(K(D))).toISOString()}}:void 0;R(ee,!!r),R(te,r?r.trigger===`quota`?`After fresh quota becomes available, when the session is idle`:ge(r.at)+` — not before this time; when quota and idle allow`:``,!0);let i=await ya(`prepare`,{session:t.session,...a()?{review:a()}:{},offer:n,...e?{sendId:e.id}:{},...r?{schedule:r}:{},...r&&K(x)?{editId:K(S)}:{},...K(u).kind===`approval`||K(u).kind===`profile`?{choice:K(f)}:{answers:e?[e.text]:[...K(d)]}},M.signal);K(u)?.id!==n||M.signal.aborted||K(A)||o()||K(ce)||(R(p,i.confirmation,!0),R(m,Date.parse(i.expires),!0))}catch(e){M.signal.aborted||R(_,e instanceof Error?e.message:`Unable to prepare action.`,!0)}finally{R(g,!1)}$.data?.noConfirm&&K(p)&&!M.signal.aborted&&await N()}async function N(){if(!K(u)?.id||K(g)||o()||K(ce)||!K(de))return;let e=K(u).id,n=K(u).kind,r=K(p);R(p,``),R(g,!0),R(b,e,!0);try{await ya(`commit`,{session:t.session,...a()?{review:a()}:{},offer:e,confirmation:r},M.signal),R(v,!0),R(x,!1),K(ee)&&(R(b,``),await ae()),R(_,K(ee)?`Trigger set. Closing Codexometer cancels it.`:n===`profile`?`Profile decision completed.`:n===`approval`?`Decision sent.`:`Text sent.`,!0)}catch(e){R(_,e instanceof _a?e.message:`Outcome uncertain. Check Codex before taking another action; nothing was retried.`,!0)}finally{R(g,!1),R(w,``),await ae(),R(d,[],!0),R(f,null)}}var ye=ns(),be=B(ye),Se=e=>{var t=Nr();Xr(B(t),17,()=>K(O).filter(e=>e.status!==`sent`),e=>e.id,(e,t)=>{var n=qo(),r=z(n),i=V(r,!0),a=H(r,2),s=V(a),c=H(a,2),l=V(c,!0),d=H(c,2),f=V(d,!0),m=H(d,2),h=V(m,!0),v=H(m,2),y=e=>{var t=Wo(),n=V(t,!0);U(()=>X(n,K(_))),Y(e,t)};Z(v,e=>{K(_)&&e(y)});var b=H(v,4),x=H(b,2),S=H(x,2),C=e=>{var n=Go(),r=H(B(n),2),i=H(r,2);U(()=>r.disabled=K(g)||K(A)||!K(t).canSend),q(`click`,r,N),q(`click`,i,()=>{R(p,``),R(w,``)}),Y(e,n)},T=e=>{var n=Ko();U(()=>n.disabled=K(g)||K(A)||!K(t).canSend||o()),q(`click`,n,()=>ve(K(t))),Y(e,n)};Z(S,e=>{K(de)&&K(w)===K(t).id?e(C):e(T,-1)}),P(n),U((e,n)=>{X(i,K(t).status===`uncertain`?`CHECK TRIGGER — outcome uncertain; not retried`:`SCHEDULED FOLLOW-UP`),X(s,`TARGET // ${K(t).session??``}`),X(l,e),X(f,n),X(h,K(t).text),b.disabled=K(g)||!K(u)?.id||K(u).kind!==`prompt`||!!K(u).questions?.length||K(t).status!==`pending`,x.disabled=K(g)||K(t).status===`sending`},[()=>K(t).trigger===`quota`?`After confirmed quota recovery, when idle`:`Not before ${ge(K(t).at)}`,()=>_e(K(t))]),q(`click`,b,()=>ie(K(t))),q(`click`,x,()=>oe(K(t).id)),Y(e,n)}),Y(e,t)},Ce=F(()=>!a()&&K(u)?.kind!==`approval`&&!K(u)?.questions?.length&&![`APPROVAL NEEDED`,`INPUT NEEDED`].includes(K(se)||``));Z(be,e=>{K(Ce)&&e(Se)});var we=H(be,2),Te=e=>{var r=Ts(),o=z(r),s=V(o,!0),c=H(o,2),S=e=>{var t=Jo(),n=B(t),r=V(n),i=H(n,6),a=V(i,!0),o=V(H(i,6),!0);xe(2),U(()=>{X(r,`Your ${K(u).profile.threshold??``}% quota threshold has been reached. Review + the profile for subsequent turns.`),X(a,K(u).profile.current),X(o,K(u).profile.proposed)}),Y(e,t)};Z(c,e=>{K(u)?.profile&&!K(A)&&!K(y)&&e(S)});var C=H(c,2),w=e=>{var t=Yo();let n;var r=V(t,!0);U(()=>{n=li(t,1,`svelte-1oupzfc`,null,n,{notice:!K(v),sent:K(v)}),X(r,K(_))}),Y(e,t)};Z(C,e=>{K(_)&&e(w)});var O=H(C,2),ne=e=>{Uo(e,{get lines(){return K(u).fileChanges}})},ie=e=>{Uo(e,{get lines(){return i()}})},ae=e=>{var t=Xo(),n=B(t),r=V(n,!0),i=V(H(n,2),!0);U(()=>{X(r,K(le)?`EXACT COMMAND`:`LAST OBSERVED COMMAND`),X(i,K(ue))}),Y(e,t)},oe=e=>{Y(e,Zo())};Z(O,e=>{K(u)?.fileChanges?.length&&!K(A)&&!K(y)?e(ne):i().length?e(ie,1):K(ue)?e(ae,2):a()!==`profile`&&K(se)===`APPROVAL NEEDED`&&!K(v)&&e(oe,3)});var ce=H(O,2),pe=e=>{Y(e,Qo())},_e=e=>{Y(e,$o())},M=e=>{var t=ns(),n=B(t),r=e=>{var t=es(),n=V(t,!0);U(e=>X(n,e),[()=>K(se)===`WORKING`?`Codex is working — nothing to respond to.`:[`APPROVAL NEEDED`,`INPUT NEEDED`].includes(K(se)||``)?`Respond in Codex for this request.`:`Nothing needs a response right now.`]),Y(e,t)};Z(n,e=>{(K(se)===`WORKING`||!K(v)&&!K(g))&&e(r)});var i=H(n,2),a=e=>{Y(e,ts())},o=F(()=>[`APPROVAL NEEDED`,`INPUT NEEDED`].includes(K(se)||``)&&!K(v));Z(i,e=>{K(o)&&e(a)}),Y(e,t)},ye=e=>{var r=ws(),i=B(r),o=V(i),s=H(i,2),c=z(s),_=V(c,!0),v=H(c,2),y=e=>{var r=Nr();Xr(B(r),17,()=>K(u).choices||[],Kr,(e,r,i)=>{var o=as(),s=z(o);Si(s),s.value=s.__value=i;var c=H(s),l=H(c),u=e=>{var t=rs(),n=V(t,!0);U(()=>X(n,K(r).detail)),Y(e,t)};Z(l,e=>{K(r).detail&&e(u)});var d=H(l,2),p=e=>{Y(e,is())};Z(d,e=>{K(r).persistent&&e(p)}),P(o),U(()=>{Q(s,`name`,`decision-`+t.session+`-`+a()),X(c,` ${K(r).label??``} `)}),ki(n,[],s,()=>K(f),e=>R(f,e)),Y(e,o)}),Y(e,r)},b=e=>{var n=ns(),r=B(n),i=e=>{var t=fs(),n=B(t),r=H(z(n)),i=z(r),a=e=>{var t=os();t.value=t.__value=`now`,Y(e,t)};Z(i,e=>{K(x)||e(a)});var o=H(i);o.value=o.__value=`quota`;var s=H(o);s.value=s.__value=`delay`;var c=H(s);c.value=c.__value=`at`,P(r),hi(r),P(n);var l=H(n,2),u=e=>{var t=ss(),n=H(z(t));Si(n),P(t),Di(n,()=>K(E),e=>R(E,e)),Y(e,t)};Z(l,e=>{K(T)===`delay`&&e(u)});var d=H(l,2),f=e=>{var t=cs(),n=H(z(t));Si(n),P(t),Di(n,()=>K(D),e=>R(D,e)),Y(e,t)};Z(d,e=>{K(T)===`at`&&e(f)});var p=H(d,2),m=e=>{var t=us(),n=B(t),r=V(n),i=H(n,2),a=e=>{Y(e,ls())};Z(i,e=>{K(T)!==`quota`&&e(a)});var o=H(i,2);o.textContent=`Timezone: ${Intl.DateTimeFormat().resolvedOptions().timeZone??``}. + One pending request per session; saving replaces it. Memory + only; Codexometer must stay running. Uses session model, + reasoning and speed.`,U(e=>X(r,`Will send: ${e??``}`),[()=>K(T)===`quota`?`when quota is available and this session is idle`:Number.isFinite(K(me))?ge(K(me)):`choose a date and time`]),Y(e,t)};Z(p,e=>{K(T)!==`now`&&e(m)});var h=H(p,2),g=e=>{Y(e,ds())};Z(h,e=>{K(x)&&e(g)}),gi(r,()=>K(T),e=>R(T,e)),Y(e,t)};Z(r,e=>{K(u).questions?.length||e(i)}),Xr(H(r,2),17,()=>K(fe),Kr,(e,n,r)=>{var i=ys(),o=B(i),s=e=>{{let n=F(()=>K(d)[0]||``);Li(zo(e,{get session(){return t.session},get oncopy(){return l()},get query(){return K(n)},oncomplete:e=>{R(d,[e],!0),lr().then(()=>K(k)?.focus())},onchange:()=>{R(d,[``],!0),lr().then(()=>K(k)?.focus())}}),e=>R(re,e,!0),()=>K(re))}};Z(o,e=>{r===0&&a()!==`profile`&&K(u).kind===`prompt`&&!K(u).questions?.length&&e(s)});var c=H(o,2),f=z(c),p=H(f),m=e=>{var t=ps();Si(t),Di(t,()=>K(d)[r],e=>K(d)[r]=e),Y(e,t)},h=e=>{var t=hs(),i=z(t);i.value=i.__value=``,Xr(H(i),17,()=>K(n).options||[],Kr,(e,t)=>{var n=ms(),r=V(n,!0),i={};U(()=>{X(r,K(t)),i!==(i=K(t))&&(n.value=(n.__value=i)??``)}),Y(e,n)}),P(t),hi(t),gi(t,()=>K(d)[r],e=>K(d)[r]=e),Y(e,t)},g=e=>{var t=gs(),i=B(t);tt(i),Li(i,e=>R(k,e),()=>K(k));var a=H(i,2),o=e=>{var t=es(),r=V(t);U(e=>X(r,`Suggested answers: ${e??``}`),[()=>K(n).options.join(` · `)]),Y(e,t)};Z(a,e=>{K(n).options?.length&&e(o)}),q(`keydown`,i,e=>{K(u)?.kind===`prompt`&&!K(u).questions?.length&&K(re)?.handleKey(e)}),Di(i,()=>K(d)[r],e=>K(d)[r]=e),Y(e,t)};Z(p,e=>{K(n).secret?e(m):K(n).freeText?e(g,-1):e(h,1)}),P(c);var _=H(c,2),v=e=>{var t=vs(),r=H(z(t),4);Xr(r,21,()=>K(n).options||[],Kr,(e,t)=>{var n=_s(),r=V(n,!0);U(()=>X(r,K(t))),Y(e,n)}),P(r),P(t),Y(e,t)};Z(_,e=>{K(n).secret&&!K(n).freeText&&e(v)}),U(()=>X(f,`${K(n).text??``} `)),Y(e,i)}),Y(e,n)};Z(v,e=>{K(u).kind===`approval`||K(u).kind===`profile`?e(y):e(b,-1)}),P(s);var S=H(s,2),C=e=>{var t=xs(),n=B(t),r=e=>{var t=bs(),n=V(t);U(()=>X(n,`TRIGGER // ${K(te)??``}`)),Y(e,t)};Z(n,e=>{K(ee)&&e(r)});var i=H(n,2),a=V(i),o=H(i,2),s=V(o),c=H(o,2);U(e=>{X(a,`Check the target and ${K(u).kind===`profile`?`profile above`:K(u).kind===`approval`?K(u).fileChanges?.length?`proposed file changes above`:`exact command and permission scope`:`message above`}. ${K(ee)?`This schedules a follow-up that will send automatically when eligible`:`This will send to Codex`}; it may start work using your quota. + Confirmation expires in ${e??``}s.`),o.disabled=K(g)||K(A),X(s,`CONFIRM ${(K(u).kind===`approval`||K(u).kind===`profile`?K(u).choices?.[K(f)]?.label:K(ee)?K(x)?`SAVE CHANGES`:`SCHEDULE`:`SEND`)??``}`)},[()=>Math.max(0,Math.ceil((K(m)-K(h))/1e3))]),q(`click`,o,N),q(`click`,c,()=>{R(p,``)}),Y(e,t)},w=e=>{var t=Ss(),n=B(t),r=e=>{var t=Wo(),n=V(t,!0);U(()=>X(n,K(he))),Y(e,t)};Z(n,e=>{K(he)&&e(r)});var i=H(n,2),a=V(i,!0);U(()=>{i.disabled=K(g)||K(A)||!K(j)||!!K(he),X(a,K(g)?`SENDING…`:$.data?.noConfirm?K(x)?`SAVE CHANGES`:K(u).kind===`approval`||K(u).kind===`profile`?`SEND DECISION`:K(u).kind===`prompt`&&!K(u).questions?.length&&K(T)!==`now`?`SET TRIGGER`:`SEND`:K(x)?`REVIEW CHANGES`:`REVIEW BEFORE SENDING`)}),q(`click`,i,()=>ve()),Y(e,t)};Z(S,e=>{K(de)&&!$.data?.noConfirm?e(C):e(w,-1)});var O=H(S,2),ne=e=>{var t=Cs();q(`click`,t,()=>{R(x,!1),R(p,``),R(d,[],!0)}),Y(e,t)};Z(O,e=>{K(x)&&e(ne)}),U(()=>{X(o,`TARGET // ${K(u).thread??``} // ${(K(u).directory||`Directory unavailable`)??``}`),s.disabled=K(g)||K(de)||K(A),X(_,K(u).kind===`approval`||K(u).kind===`profile`?`Choose a decision`:K(x)?`EDIT SCHEDULED FOLLOW-UP`:`Reply to this session`)}),Y(e,r)};Z(ce,e=>{K(A)||K(y)?e(pe):K(u)?K(u).id?K(b)!==K(u).id&&e(ye,3):e(M,2):e(_e,1)}),P(r),U(()=>X(s,a()===`profile`?`QUOTA THRESHOLD`:`SESSION CONTROL // EXPERIMENTAL`)),Y(e,r)};Z(we,e=>{!K(ce)&&(!K(ne)||K(x)||a()||K(u)?.kind!==`prompt`||K(u).questions?.length)&&e(Te)}),Y(e,ye),We()}Sr([`click`,`keydown`]);var Ds=J(` `);function Os(e,t){Ue(t,!0);let n=Gi(t,`stale`,3,!1),r=F(()=>n()||!!t.goal?.stale);var i=Nr(),a=B(i),o=e=>{var n=Ds();let i;var a=V(n,!0);U((e,o)=>{i=li(n,1,`goal-status svelte-1yfu3xi`,null,i,{stale:K(r),pursuing:!K(r)&&t.goal.status===`active`,blocked:e}),Q(n,`title`,o),X(a,K(r)?`GOAL STALE`:t.goal.label)},[()=>!K(r)&&[`blocked`,`usageLimited`,`budgetLimited`].includes(t.goal.status),()=>t.goal.objective+` +`+t.goal.tokensUsed.toLocaleString()+(t.goal.tokenBudget===null?` tokens`:` / `+t.goal.tokenBudget.toLocaleString()+` tokens`)]),Y(e,n)};Z(a,e=>{t.goal&&e(o)}),Y(e,i),We()}var ks=J(` `,1),As=J(`
      `),js=J(`

      STATUS LINE // CODEXOMETER

      Choose multiple fields and their order. These display preferences do not + change Codex CLI configuration. Missing values are omitted.

      `),Ms=J(`

      `,1);function Ns(e,t){Ue(t,!0);let n=Gi(t,`stale`,3,!1),r=L(Yt([])),i=L(!1),a=F(()=>$.data?.statusLineFields||[]),o=e=>[...new Set(e??K(a).filter(e=>e.default).map(e=>e.id))].filter(e=>K(a).some(t=>t.id===e)),s=e=>o(e).map(e=>e===`run-state`&&(n()||!$.connected||$.data?.sessionsError)?`STALE`:t.session.statusLine?.[e]).filter(Boolean).join(` · `),c=F(()=>[...K(r).map(e=>K(a).find(t=>t.id===e)).filter(Boolean),...K(a).filter(e=>!K(r).includes(e.id))]);vn(()=>{sa.open&&!K(i)?(R(r,o(oa.statusLine),!0),R(i,!0)):sa.open||R(i,!1)});function l(e,t){R(r,t?[...K(r),e]:K(r).filter(t=>t!==e),!0)}function u(e,t){let n=K(r).indexOf(e),i=n+t;if(n<0||i<0||i>=K(r).length)return;let a=[...K(r)];[a[n],a[i]]=[a[i],a[n]],R(r,a,!0)}var d=Ms(),p=B(d),m=e=>{var t=js(),n=H(z(t),4);Xr(n,21,()=>K(c),e=>e.id,(e,t)=>{var n=As(),i=z(n),a=z(i);Si(a);var o=H(a,1,!0),s=V(H(o),!0);P(i);var c=H(i,2),d=e=>{var n=ks(),i=B(n),a=H(i,2);U((e,n)=>{Q(i,`aria-label`,`Move `+K(t).label+` earlier`),i.disabled=e,Q(a,`aria-label`,`Move `+K(t).label+` later`),a.disabled=n},[()=>K(r).indexOf(K(t).id)===0,()=>K(r).indexOf(K(t).id)===K(r).length-1]),q(`click`,i,()=>u(K(t).id,-1)),q(`click`,a,()=>u(K(t).id,1)),Y(e,n)},f=F(()=>K(r).includes(K(t).id));Z(c,e=>{K(f)&&e(d)}),P(n),U(e=>{Q(i,`title`,K(t).help),Ci(a,e),X(o,K(t).label),X(s,K(t).help)},[()=>K(r).includes(K(t).id)]),q(`change`,a,e=>l(K(t).id,e.currentTarget.checked)),Y(e,n)}),P(n);var i=H(n,2),a=V(i),o=H(i,2),d=H(o,2);P(t),U(e=>X(a,`Preview // ${e??``}`),[()=>s(K(r))||`—`]),q(`click`,o,()=>{oa.statusLine=[...K(r)],la(),sa.open=!1}),q(`click`,d,()=>{sa.open=!1}),Y(e,t)};Z(p,e=>{sa.open&&e(m)});var h=H(p,2),g=z(h),_=V(g,!0),v=H(g,2);{let e=F(()=>n()||!$.connected||!!$.data?.sessionsError);Os(v,{get goal(){return t.session.goal},get stale(){return K(e)}})}var y=H(v,2);ni(H(y,2),()=>t.children??f),P(h),U((e,t)=>{Q(g,`title`,e),X(_,t)},[()=>s(oa.statusLine),()=>s(oa.statusLine)]),q(`click`,y,()=>{sa.open=!sa.open}),Y(e,d),We()}Sr([`change`,`click`]);var Ps=J(`
      `);function Fs(e,t){Ue(t,!0);let n=Gi(t,`active`,3,!1),r=Gi(t,`compact`,3,!1),i=F(()=>oo(t.session)),a=F(()=>!!K(i)),o=L(!1),s=L(``),c=L(!1);vn(()=>{if(!K(c))return;let e=setTimeout(()=>{R(c,!1)},150);return()=>clearTimeout(e)}),vn(()=>{K(i),t.session.status,R(s,``)});async function l(){if(!K(a)||K(o))return;let e=K(i);R(o,!0),R(c,!0),R(s,``);try{await navigator.clipboard.writeText(e),K(i)===e&&R(s,`Copied.`)}catch{K(i)===e&&R(s,`Clipboard unavailable. Select the visible text and copy it manually.`)}finally{R(o,!1)}}function u(e){!n()||!K(a)||e.defaultPrevented||e.repeat||e.ctrlKey||e.metaKey||e.altKey||e.key.toLowerCase()!==`c`||e.target instanceof Element&&e.target.closest(`input, textarea, select, [contenteditable]`)||(e.preventDefault(),l())}var d={copy:l},f=Nr();xr(`keydown`,Qt,u);var p=B(f),m=e=>{var t=Ps();let n;var i=z(t),a=V(i,!0),u=H(i,2);let d;P(t),U(()=>{n=li(t,1,`session-copy svelte-543j00`,null,n,{compact:r()}),X(a,K(s)),u.disabled=K(o),d=li(u,1,`svelte-543j00`,null,d,{flashed:K(c)})}),q(`click`,u,l),Y(e,t)};return Z(p,e=>{K(a)&&e(m)}),Y(e,f),We(d)}Sr([`click`]);var Is=J(`

       
      `,1),Ls=J(`

      LATEST GUIDANCE

       
      `,1),Rs=J(`

      CONTEXT

       

      `,1),zs=J(`

      `),Bs=J(`

      `),Vs=J(`
       
      `),Hs=J(`

       
      `,1),Us=J(`
       
      `),Ws=J(`

      Command unavailable from this observation. Open Codex to inspect the + request.

      `),Gs=J(`

      `,1),Ks=J(`

      `),qs=J(` `,1),Js=J(`
      `),Ys=J(`

      Session connection or refresh unavailable. Context and telemetry may be + stale.

      `),Xs=J(`

      Some quota profile checks are unavailable. Only sessions with freshly + verified quota and settings can be updated; previous outcome notices remain + visible.

      `),Zs=J(` `),Qs=J(` `),$s=J(` `),ec=J(``),tc=J(`

      Read only — reply or approve in Codex.

      `),nc=J(`

      `),rc=J(`QUOTA THRESHOLD // REVIEW PROFILE ↗`),ic=J(`
      `),ac=J(` `,1),oc=J(`
      `),sc=J(`

      This session is no longer in the current observation. Return to sessions.

      `),cc=J(`

      `),lc=J(``),uc=J(`

      `),dc=J(` `),fc=J(`

      `),pc=J(`QUOTA THRESHOLD // REVIEW PROFILE ↗`),mc=J(`

      `),hc=J(`

      TOKEN ACTIVITY // 30 SECOND SAMPLES

      `),gc=J(`

      TOKENS

      FULL DETAIL →
      `),_c=J(`

      No locally observed sessions yet. Keep Codex running alongside + Codexometer.

      `),vc=J(`

      ↑ ↓ SELECT SESSION // ← LESS DETAIL // → MORE DETAIL // ESC BACK

      `,1),yc=J(`

      SESSION TOTALS

      `,1);function bc(e,t){Ue(t,!0);let n=(e,t=f,n,r)=>{let i=pt(()=>g(n?.(),!0)),a=pt(()=>g(r?.(),!1));var o=qs(),s=B(o),c=e=>{var n=Is(),r=B(n),i=V(r,!0),a=V(H(r,2),!0);U(()=>{X(i,t().contextKind===`LAST REPLY`&&!t().streaming?`TASK`:`CURRENT TASK`),X(a,t().currentTask)}),Y(e,n)};Z(s,e=>{t().currentTask&&e(c)});var l=H(s,2),u=e=>{var n=Ls(),r=V(H(B(n),2),!0);U(()=>X(r,t().latestGuidance)),Y(e,n)};Z(l,e=>{t().latestGuidance&&e(u)});var d=H(l,2),p=e=>{var n=Rs(),r=V(H(B(n),2),!0);xe(2),U(()=>X(r,t().approvalContext)),Y(e,n)};Z(d,e=>{K(a)&&t().contextKind===`APPROVAL REQUEST`&&t().approvalContext&&e(p)});var m=H(d,2),h=e=>{var n=zs();let r;var i=V(n,!0);U(e=>{r=li(n,1,`attention-note`,null,r,{inferred:t().status===`CHECK SESSION`}),X(i,e)},[()=>D(t())]),Y(e,n)},_=F(()=>D(t())&&(!K(a)||K(v)||t().status===`CHECK SESSION`||y(t())));Z(m,e=>{K(_)&&e(h)});var b=H(m,2),x=e=>{var n=Bs(),r=V(n,!0);U(e=>X(r,e),[()=>ee(t())]),Y(e,n)};Z(b,e=>{K(i)&&e(x)});var S=H(b,2),C=e=>{var n=Vs(),r=V(n,!0);U(()=>X(r,t().text||`No session context available.`)),Y(e,n)};Z(S,e=>{(t().text||!t().workingCommand)&&e(C)});var w=H(S,2),T=e=>{var n=Hs(),r=H(B(n),2),i=z(r),a=H(i),o=e=>{var n=Mr();U(()=>X(n,`// ${t().commandTiming??``}`)),Y(e,n)};Z(a,e=>{t().commandTiming&&e(o)});var s=H(a,2),c=e=>{var n=Mr();U(()=>X(n,`// +${(t().runningCommands||0)-+(t().commandStatus===`running`)} RUNNING`)),Y(e,n)};Z(s,e=>{!K(v)&&(t().runningCommands||0)>+(t().commandStatus===`running`)&&e(c)});var l=H(s,2),u=e=>{Y(e,Mr(`+`))};Z(l,e=>{t().runningLimited&&e(u)}),P(r);var d=V(H(r,2),!0);U(e=>{X(i,`COMMAND // ${e??``} `),X(d,t().workingCommand)},[()=>K(v)?`UNKNOWN`:(t().commandStatus||`unknown`).toUpperCase()]),Y(e,n)};Z(w,e=>{t().workingCommand&&e(T)});var E=H(w,2),te=e=>{Uo(e,{get lines(){return t().fileChanges}})},O=e=>{var n=Gs(),r=H(B(n),2),i=V(r,!0),a=H(r,2),o=e=>{var n=Us(),r=V(n,!0);U(()=>X(r,t().command)),Y(e,n)},s=e=>{Y(e,Ws())};Z(a,e=>{t().command?e(o):e(s,-1)}),U(()=>X(i,K(v)||t().status!==`APPROVAL NEEDED`?`LAST OBSERVED COMMAND`:$.data?.control?`COMMAND REQUEST`:`COMMAND TO APPROVE IN CODEX`)),Y(e,n)};Z(E,e=>{(!K(a)||!$.data?.control)&&t().fileChanges?.length?e(te):K(a)&&$.data?.control||!t().command&&t().status!==`APPROVAL NEEDED`||e(O,1)});var ne=H(E,2),k=e=>{var n=Ks(),r=V(n);U(()=>X(r,`CONTEXT SOURCE // ${(t().source||`LOCAL`)??``} // OBSERVATION`)),Y(e,n)};Z(ne,e=>{K(a)||e(k)}),Y(e,o)},r=L(void 0),i=Gi(t,`params`,19,()=>({})),a=F(()=>$.data?.sessions||[]),o=F(()=>$.data?.control&&$.data.profiles||[]),s=F(()=>($.data?.triggers||[]).filter(e=>K(a).some(t=>t.id===e.session)));function c(e,t){i().id&&i().id!==t&&K(u)?e.preventDefault():w(t)}function l(e){return[`TURN COMPLETE`,`IDLE`,`ACTIVE`].includes(e.status)&&K(s).some(t=>t.session===e.id)?`TRIGGER SET`:e.status}let u=L(!1);function d(e,t){i().id&&i().id!==t&&K(u)?e.preventDefault():w(t)}vn(()=>{let e=i().id;e&&fr(()=>{oa.selected=e,pa(e,2)})});let p=F(()=>K(a).find(e=>e.id===i().id)),m=F(()=>new URLSearchParams(Xi.querystring).get(`review`)===`profile`&&K(o).some(e=>e.session===i().id&&e.pending)),h=F(()=>K(a).some(e=>e.id===oa.selected)?oa.selected:K(a)[0]?.id),v=F(()=>!$.connected||!!$.data?.sessionsError);function y(e){return e.status!==`STALE`&&e.failed===!0}let b=F(()=>[[`OBSERVED TOKENS`,ma(K(a).reduce((e,t)=>e+t.tokens,0))],[`LISTED SESSIONS`,ma(K(a).length)],...[[`WORKING`,`WORKING`],[`AWAITING APPROVAL`,`APPROVAL NEEDED`],[`AWAITING INPUT`,`INPUT NEEDED`],[`ERRORS`,`ERRORS`],[`CHECK · INFERRED`,`CHECK SESSION`]].map(([e,t])=>[e,K(v)?`—`:ma(K(a).filter(e=>t===`ERRORS`?y(e):e.status===t).length)])]),x=F(()=>K(a).filter(e=>[`INPUT NEEDED`,`APPROVAL NEEDED`].includes(e.status)||y(e)).sort((e,t)=>S(e)-S(t)));function S(e){return e.status===`APPROVAL NEEDED`?0:e.status===`INPUT NEEDED`?1:2}let C=F(()=>K(a).filter(e=>e.status===`TURN COMPLETE`&&!K(s).some(t=>t.session===e.id)&&!K(o).some(t=>t.session===e.id&&t.pending)));function w(e){oa.selected=e,lr().then(()=>document.querySelector(`.session-row.selected`)?.scrollIntoView({block:`nearest`}))}function T(e,t){if(w(e),i().id){t<0&&(pa(e,2),location.hash=`/sessions`);return}let n=da(e)+t;n>2?location.hash=`/sessions/`+encodeURIComponent(e):pa(e,n)}function E(e){let t=e.target;if(!(e.altKey||e.ctrlKey||e.metaKey||e.shiftKey||t.closest(`input, textarea, select, [contenteditable="true"]`))){if(e.key===`Escape`&&i().id)e.preventDefault(),T(i().id,-1);else if([`ArrowLeft`,`ArrowRight`].includes(e.key)&&(i().id||K(h)))e.preventDefault(),T(i().id||K(h),e.key===`ArrowLeft`?-1:1);else if(!i().id&&[`ArrowUp`,`ArrowDown`].includes(e.key)&&K(a).length){e.preventDefault();let t=K(a).findIndex(e=>e.id===K(h));w(K(a)[Math.max(0,Math.min(K(a).length-1,t+(e.key===`ArrowUp`?-1:1)))].id)}}}function D(e){return K(v)?`Last observation only — refresh unavailable. Check Codex for current state.`:e.status===`CHECK SESSION`?`INFERRED INACTIVITY — a quiet session, not a confirmed input or approval request. Local tools may still be running.`:y(e)?e.status===`MODEL BUSY`?`CONFIRMED TURN FAILURE — the selected model was at capacity. Choose another model with /model or retry when ready. This is not a quota-exhaustion signal.`:e.status===`MODEL UNAVAILABLE`?`CONFIRMED TURN FAILURE — the provider could not find the selected model. Choose an available model with /model. No automatic retry or model change.`:e.status===`CAPACITY RESTRICTED`?`CONFIRMED TURN FAILURE — capacity is unavailable on a policy-restricted route. Inspect the error before changing settings or retrying. This is not a quota-exhaustion signal; no automatic retry or policy change.`:`CONFIRMED TURN FAILURE — inspect the error below before sending another prompt. No automatic retry or model change.`:$.data?.control&&[`APPROVAL NEEDED`,`INPUT NEEDED`].includes(e.status)?`Open full detail for supported live controls; otherwise reply in Codex.`:e.status===`APPROVAL NEEDED`?`OBSERVED APPROVAL SIGNAL — approve or decline in Codex.`:e.status===`INPUT NEEDED`?`OBSERVED INPUT SIGNAL — reply in Codex.`:e.status===`TURN COMPLETE`?`OBSERVED TURN COMPLETION — informational, not an approval request.`:``}function ee(e){return e.streaming&&e.contextKind===`LAST REPLY`?`REPLY // STREAMING`:e.contextKind||`LAST ACTIVITY`}var te=yc();xr(`keydown`,Qt,E);var O=B(te),ne=V(H(z(O),2));P(O);var k=H(O,2);let re;Xr(k,21,()=>K(b),Kr,(e,t)=>{var n=F(()=>_(K(t),2));let r=()=>K(n)[0],i=()=>K(n)[1];var a=Js();let o;var s=z(a),c=V(s,!0),l=V(H(s,2),!0);P(a),U(()=>{o=li(a,1,`svelte-mm4wfe`,null,o,{failed:r()===`ERRORS`&&i()!==`0`&&i()!==`—`}),X(c,r()),X(l,i())}),Y(e,a)}),P(k);var ie=H(k,2),ae=V(ie),oe=H(ie,2),se=e=>{Y(e,Ys())};Z(oe,e=>{K(v)&&e(se)});var A=H(oe,2),ce=e=>{Y(e,Xs())};Z(A,e=>{$.data?.profileError&&e(ce)});var le=H(A,2),ue=e=>{var t=ec(),n=z(t);Xr(n,17,()=>K(x),Kr,(e,t)=>{var n=Zs();let r;var i=V(n);U((e,a,o)=>{r=li(n,1,`button`,null,r,{approval:e}),Q(n,`href`,a),X(i,`${K(t).status??``} + ${o??``} // ${(K(t).name||K(t).directory||K(t).id)??``}`)},[()=>K(t).status===`APPROVAL NEEDED`||K(t).status===`INPUT NEEDED`||y(K(t)),()=>`#/sessions/`+encodeURIComponent(K(t).id),()=>Array.from(K(t).id).slice(-5).join(``).toUpperCase()]),q(`click`,n,()=>w(K(t).id)),Y(e,n)});var r=H(n,2);Xr(r,17,()=>K(o).filter(e=>e.pending&&K(a).some(t=>t.id===e.session)),Kr,(e,t)=>{var n=Qs(),r=V(n);U((e,a,o)=>{Q(n,`title`,i().id&&i().id!==K(t).session&&K(u)?`Finish sending or clear your current draft before switching sessions.`:`Review this session’s quota threshold`),Q(n,`href`,e),X(r,`QUOTA THRESHOLD ${a??``} // ${o??``}`)},[()=>`#/sessions/`+encodeURIComponent(K(t).session)+`?review=profile`,()=>Array.from(K(t).session).slice(-5).join(``).toUpperCase(),()=>K(a).find(e=>e.id===K(t).session)?.name||K(a).find(e=>e.id===K(t).session)?.directory||K(t).session]),q(`click`,n,e=>d(e,K(t).session)),Y(e,n)});var l=H(r,2);Xr(l,17,()=>K(C),Kr,(e,t)=>{var n=$s(),r=V(n);U((e,i)=>{Q(n,`href`,e),X(r,`TURN COMPLETE ${i??``} // + ${(K(t).name||K(t).directory||K(t).id)??``}`)},[()=>`#/sessions/`+encodeURIComponent(K(t).id),()=>Array.from(K(t).id).slice(-5).join(``).toUpperCase()]),q(`click`,n,()=>w(K(t).id)),Y(e,n)}),Xr(H(l,2),17,()=>K(s),Kr,(e,t)=>{var n=$s(),r=V(n);U((e,t,i)=>{Q(n,`href`,e),X(r,`TRIGGER SET ${t??``} // ${i??``}`)},[()=>`#/sessions/`+encodeURIComponent(K(t).session),()=>Array.from(K(t).session).slice(-5).join(``).toUpperCase(),()=>K(a).find(e=>e.id===K(t).session)?.name||K(a).find(e=>e.id===K(t).session)?.directory]),q(`click`,n,e=>c(e,K(t).session)),Y(e,n)}),P(t),Y(e,t)},de=F(()=>(K(x).length||K(C).length||K(o).some(e=>e.pending)||K(s).length)&&!K(v));Z(le,e=>{K(de)&&e(ue)});var fe=H(le,2),j=e=>{var t=Nr(),a=B(t),s=e=>{var t=oc(),a=z(t),s=z(a);let c;var d=z(s);let f;var h=H(d);P(s);var g=H(s,2);P(a);var _=H(a,2),b=e=>{var t=Ks(),n=V(t,!0);U(()=>X(n,K(p).directory)),Y(e,t)};Z(_,e=>{K(p).name&&K(p).directory&&e(b)});var x=H(_,2),S=V(x),C=H(x,2);let T;var E=z(C),D=z(E);n(D,()=>K(p),()=>!0,()=>!0),P(E);var ee=H(E,2),te=e=>{var t=Nr();Gr(B(t),()=>K(p).id,e=>{{let t=F(()=>new URLSearchParams(Xi.querystring).get(`schedule`)===`edit`),n=F(()=>K(p).fileChanges||[]);Es(e,{get session(){return K(p).id},oncopy:()=>K(r)?.copy(),get openScheduler(){return K(t)},get observedCommand(){return K(p).command},get observedFileChanges(){return K(n)},get suspended(){return K(m)},onProtectedChange:e=>{R(u,e,!0)}})}}),Y(e,t)},O=e=>{var t=tc();Gr(H(z(t),2),()=>K(p).id,e=>{zo(e,{get session(){return K(p).id},localOnly:!0,oncopy:()=>K(r)?.copy()})}),P(t),Y(e,t)};Z(ee,e=>{$.data?.control?e(te):e(O,-1)}),P(C);var ne=H(C,2);Xr(ne,17,()=>K(o).filter(e=>e.session===K(p).id),e=>e.session,(e,t)=>{var n=ic(),r=z(n),i=e=>{var n=nc(),r=V(n,!0);U(()=>X(r,K(t).notice)),Y(e,n)};Z(r,e=>{K(t).notice&&e(i)});var a=H(r,2),o=e=>{Es(e,{get session(){return K(p).id},review:`profile`})},s=e=>{var t=rc();U(e=>Q(t,`href`,e),[()=>`#/sessions/`+encodeURIComponent(K(p).id)+`?review=profile`]),Y(e,t)};Z(a,e=>{K(t).pending&&K(m)?e(o):K(t).pending&&e(s,1)}),P(n),U(()=>Q(n,`id`,`quota-profile-`+K(p).id)),Y(e,n)});var k=H(ne,2),re=e=>{var t=Nr();Gr(B(t),()=>K(p).id,e=>{Ns(e,{get session(){return K(p)},get stale(){return K(v)},children:e=>{var t=Nr(),n=B(t),i=e=>{Li(Fs(e,{get session(){return K(p)},active:!0,compact:!0}),e=>R(r,e,!0),()=>K(r))};Z(n,e=>{K(m)||e(i)}),Y(e,t)},$$slots:{default:!0}})}),Y(e,t)},ie=e=>{var t=ac(),n=B(t),i=e=>{var t=Nr();Gr(B(t),()=>K(p).id,e=>{Li(Fs(e,{get session(){return K(p)},active:!0}),e=>R(r,e,!0),()=>K(r))}),Y(e,t)};Z(n,e=>{K(m)||e(i)}),Gr(H(n,2),()=>K(p).id,e=>{Ns(e,{get session(){return K(p)},get stale(){return K(v)}})}),Y(e,t)};Z(k,e=>{K(p).goal?e(re):e(ie,-1)}),P(t),U((e,t,n,r)=>{c=li(s,1,`svelte-mm4wfe`,null,c,{failed:e}),f=li(d,1,`lamp lit svelte-mm4wfe`,null,f,{failed:t,working:K(p).status===`WORKING`&&!K(v)}),X(h,`${n??``} // ${(K(p).name||K(p).directory)??``}`),X(S,`${r??``} TOKENS // ${K(p).id??``} // CONTEXT SOURCE // ${(K(p).source||`LOCAL`)??``}`),T=di(C,``,T,{display:K(m)?`none`:void 0})},[()=>y(K(p))&&!K(v)&&!K(m),()=>y(K(p))&&!K(v),()=>K(v)?`STALE`:K(m)?`QUOTA THRESHOLD`:l(K(p)),()=>ma(K(p).tokens)]),q(`click`,g,()=>{w(i().id),pa(i().id,2)}),Y(e,t)},c=e=>{Y(e,sc())};Z(a,e=>{K(p)?e(s):e(c,-1)}),Y(e,t)},pe=e=>{var t=vc(),r=B(t),i=H(z(r),2),s=z(i),c=H(s,2);P(i),P(r);var u=H(r,2);Xr(u,17,()=>K(a),e=>e.id,(e,t)=>{let r=F(()=>da(K(t).id));var i=gc();let a;var s=z(i),c=z(s),u=e=>{var n=cc(),r=z(n),i=V(r);P(n),U(e=>{Q(r,`aria-pressed`,K(h)===K(t).id),X(i,`${e??``} // ${K(t).name??``}`)},[()=>Array.from(K(t).id).slice(-5).join(``).toUpperCase()]),q(`click`,r,()=>w(K(t).id)),Y(e,n)};Z(c,e=>{K(t).name&&e(u)});var f=H(c,2),p=z(f);let m;var g=z(p);let _;var b=H(g,1,!0);P(p),P(f);var x=H(f,2),S=e=>{var n=lc(),r=V(n,!0);U(()=>{Q(n,`aria-pressed`,K(h)===K(t).id),X(r,K(t).directory||K(t).id)}),q(`click`,n,()=>w(K(t).id)),Y(e,n)};Z(x,e=>{K(t).name||e(S)});var C=H(x,2),E=z(C);xe(),P(C);var D=H(C,2),te=e=>{var n=Ks(),r=V(n,!0);U(()=>X(r,K(t).directory)),Y(e,n)};Z(D,e=>{K(t).name&&K(t).directory&&e(te)});var O=H(D,2),ne=V(O),k=H(O,2),re=V(k),ie=H(k,2);Os(ie,{get goal(){return K(t).goal},get stale(){return K(v)}});var ae=H(ie,2),oe=z(ae),se=H(oe,2),A=V(se,!0),ce=H(se,2);P(ae);var le=H(ae,2),ue=H(le,2),de=e=>{var n=uc(),r=V(n,!0);U(e=>X(r,e),[()=>K(t).status===`CHECK SESSION`?`INFERRED INACTIVITY`:y(K(t))?`OPEN DETAIL FOR THE TURN ERROR`:$.data?.control?`OPEN FULL DETAIL OR REPLY IN CODEX`:K(t).status===`APPROVAL NEEDED`?`APPROVE OR DECLINE IN CODEX`:`REPLY IN CODEX`]),Y(e,n)},fe=F(()=>!K(v)&&([`APPROVAL NEEDED`,`INPUT NEEDED`,`CHECK SESSION`].includes(K(t).status)||y(K(t))));Z(ue,e=>{K(fe)&&e(de)}),P(s);var j=H(s,2),pe=e=>{var r=mc(),i=z(r),a=z(i),s=V(a,!0),c=H(a,2),l=e=>{var n=dc(),r=V(n,!0);U(e=>{Q(n,`href`,e),X(r,$.data?.control?`REVIEW IN FULL DETAIL ↗`:`APPROVAL IN CODEX ↗`)},[()=>`#/sessions/`+encodeURIComponent(K(t).id)]),Y(e,n)};Z(c,e=>{!K(v)&&K(t).status===`APPROVAL NEEDED`&&e(l)}),P(i);var u=H(i,2);n(u,()=>K(t),()=>!1);var f=H(u,2);Xr(f,17,()=>K(o).filter(e=>e.session===K(t).id),Kr,(e,n)=>{var r=ac(),i=B(r),a=e=>{var t=fc(),r=V(t,!0);U(()=>X(r,K(n).notice)),Y(e,t)};Z(i,e=>{K(n).notice&&e(a)});var o=H(i,2),s=e=>{var n=pc();U(e=>Q(n,`href`,e),[()=>`#/sessions/`+encodeURIComponent(K(t).id)+`?review=profile`]),q(`click`,n,e=>d(e,K(t).id)),Y(e,n)};Z(o,e=>{K(n).pending&&e(s)}),Y(e,r)});var p=H(f,2);{let e=F(()=>K(h)===K(t).id);Fs(p,{get session(){return K(t)},get active(){return K(e)}})}P(r),U(e=>X(s,e),[()=>ee(K(t))]),Y(e,r)};Z(j,e=>{K(r)>0&&e(pe)});var me=H(j,2),he=e=>{var n=hc(),r=H(z(n),2);{let e=F(()=>(K(t).samples||[]).map(e=>e.tokens));ao(r,{get values(){return K(e)},capacity:120})}var i=V(H(r,2));P(n),U(()=>X(i,`LAST ${(K(t).samples?.length||0)??``} SAMPLES // AUTO SCALE`)),Y(e,n)};Z(me,e=>{K(r)<2&&e(he)}),P(i),U((e,n,o,s,c,l,u)=>{a=li(i,1,`session-row`,null,a,{selected:K(h)===K(t).id,wide:K(r)===2,split:K(r)===1}),Q(i,`aria-label`,`Session `+(K(t).name||K(t).directory||K(t).id)),m=li(p,1,`status-detail svelte-mm4wfe`,null,m,{failed:e}),Q(p,`href`,n),_=li(g,1,`lamp lit svelte-mm4wfe`,null,_,{failed:o,working:K(t).status===`WORKING`&&!K(v)}),X(b,s),X(E,`${c??``} `),X(ne,`${K(t).agents??``} LINKED AGENTS`),X(re,`ACTIVE // ${l??``}`),oe.disabled=K(r)===0,Q(se,`aria-expanded`,K(r)>0),X(A,K(r)?`HIDE DETAIL`:`SHOW DETAIL`),Q(le,`href`,u)},[()=>y(K(t))&&!K(v),()=>`#/sessions/`+encodeURIComponent(K(t).id),()=>y(K(t))&&!K(v),()=>K(v)?`STALE`:l(K(t)),()=>ma(K(t).tokens),()=>ha(K(t).activity),()=>`#/sessions/`+encodeURIComponent(K(t).id)]),q(`click`,p,()=>w(K(t).id)),q(`click`,oe,()=>T(K(t).id,-1)),q(`click`,se,()=>{w(K(t).id),pa(K(t).id,+!K(r))}),q(`click`,ce,()=>T(K(t).id,1)),q(`click`,le,()=>w(K(t).id)),Y(e,i)});var f=H(u,2),p=e=>{Y(e,_c())};Z(f,e=>{K(a).length||e(p)}),q(`click`,s,()=>fa(1)),q(`click`,c,()=>fa(0)),Y(e,t)};Z(fe,e=>{i().id?e(j):e(pe,-1)}),U(e=>{X(ne,`OBSERVED ${e??``}`),re=li(k,1,`session-totals`,null,re,{stale:K(v)}),X(ae,`${K(v)?`LAST KNOWN TOTALS — live state counts unavailable. `:``}Tokens + observed since this server started for currently listed sessions; linked + agents are already included. Totals can decrease when a session leaves the + list. History samples every 30 seconds. Not account-wide totals.`)},[()=>ha($.data?.sessionsAt)]),Y(e,te),We()}Sr([`click`]);var xc=J(``),Sc=J(`

      `),Cc=J(`

      Relative usage/credits are not tokens or quota percentages. Groupings + describe the same usage, not additional consumption.

      `,1),wc=J(`// APPROXIMATE`),Tc=J(`

      Partial accounting: totals may still change.

      `),Ec=J(`

      Percentage of this period's historical allowance, not today's limit. May + exceed 100%. This is not a manual/automatic reset log.

      `,1),Dc=J(`

      `,1),Oc=J(`

      This optional account report is unavailable. Token history remains + available; missing data does not mean zero usage.

      `),kc=J(`
      `),Ac=J(`
      `),jc=J(`

      No usage reported for this day.

      `),Mc=J(`

      Selected breakdown unavailable; missing does not mean zero.

      `),Nc=J(`

      `,1);function Pc(e,t){Ue(t,!0);let n=L(Yt(co[0])),r=L(0),i=F(()=>t.reports?.daily),a=F(()=>t.reports?.plan),o=F(()=>t.mode===`breakdown`?K(i)?.days?.length||0:K(a)?.periods?.length||0),s=F(()=>Math.min(K(r),Math.max(0,K(o)-1))),c=F(()=>K(i)?.days?.[(K(i)?.days?.length||0)-1-K(s)]),l=F(()=>K(a)?.periods?.[K(s)]),u=F(()=>t.mode===`breakdown`?K(i)?.units||``:`%`),d=F(()=>(t.mode===`breakdown`?K(c):K(l))?.views?.[K(n)]),f=F(()=>K(d)?.rows||[]),p=F(()=>K(d)?.total),m=F(()=>K(d)?.level||`unknown`),h=F(()=>K(d)?.peak||1),g=e=>e==null?`UNKNOWN`:e.toLocaleString(`en-GB`,{maximumFractionDigits:2});var _=Nc(),v=B(_),y=z(v),b=H(z(y));Xr(b,21,()=>co,Kr,(e,t)=>{var n=xc(),r=V(n,!0),i={};U(e=>{X(r,e),i!==(i=K(t))&&(n.value=(n.__value=i)??``)},[()=>K(t).charAt(0).toUpperCase()+K(t).slice(1)]),Y(e,n)}),P(b),hi(b),P(y);var x=H(y,2),S=H(x,2),C=V(H(S,2),!0);P(v);var w=H(v,2),T=z(w),E=V(T,!0),D=H(T,2),ee=V(D),te=H(D,2),O=e=>{var t=Cc(),n=B(t),r=V(n),a=H(n,2),o=e=>{var t=Sc(),n=V(t);U(()=>X(n,`Data as of ${K(i).dataAsOf??``}`)),Y(e,t)};Z(a,e=>{K(i).dataAsOf&&e(o)});var s=V(H(a,2));xe(2),U((e,t)=>{X(r,`Latest fetch ${e??``} // queried ${K(i).from??``} → ${K(i).through??``}. + Reports are fetched from OpenAI and are not saved between runs.`),X(s,`${(K(c)?.date||`No daily buckets reported`)??``} // ${t??``} + ${K(u)??``}`)},[()=>ha(K(i).fetchedAt),()=>g(K(p))]),Y(e,t)},ne=e=>{var t=Dc(),n=B(t),r=z(n);let i;var o=V(r,!0),s=H(r),c=e=>{Y(e,wc())};Z(s,e=>{K(a).approximate&&e(c)});var u=H(s);P(n);var d=H(n,2),f=z(d),h=H(f),_=e=>{var t=Mr();U(()=>X(t,`// Boundary tolerance ${K(a).boundary_tolerance_seconds??``}s`)),Y(e,t)};Z(h,e=>{K(a).boundary_tolerance_seconds!=null&&e(_)}),P(d);var v=H(d,2),y=e=>{var t=Ec(),n=B(t),r=z(n),i=H(r);let a;var o=V(i);P(n);var s=H(n,2),c=V(s),u=H(s,2),d=e=>{Y(e,Tc())};Z(u,e=>{K(l).accounting_complete||e(d)}),xe(2),U(e=>{X(r,`${K(l).window_minutes??``} MIN // ${K(l).plan_type??``} // `),a=li(i,1,`svelte-1fbui7h`,null,a,{muted:K(m)===`unknown`,"quota-near":K(m)===`near`,"quota-full":K(m)===`full`}),X(o,`USED ${e??``}${K(p)==null?``:`%`}`),X(c,`${K(l).starts_at??``} → ${K(l).ends_at??``}`)},[()=>g(K(p))]),Y(e,t)};Z(v,e=>{K(l)&&e(y)}),U(e=>{i=li(r,1,`svelte-1fbui7h`,null,i,{"quality-warning":!K(a).coverage_complete}),X(o,K(a).coverage_complete?`Reported range complete`:`Partial coverage`),X(u,` // Latest fetch ${e??``}`),X(f,`Coverage starts ${(K(a).coverage_start||`unknown`)??``} // Data as of ${(K(a).data_as_of||`unknown`)??``}`)},[()=>ha(K(a).fetchedAt)]),Y(e,t)},k=e=>{Y(e,Oc())};Z(te,e=>{t.mode===`breakdown`&&K(i)?e(O):t.mode===`periods`&&K(a)?e(ne,1):e(k,-1)});var re=H(te,2),ie=e=>{var n=Ac(),r=z(n),i=V(H(z(r),2),!0);P(r),Xr(H(r,2),17,()=>K(f),Kr,(e,n,r)=>{let i=()=>K(n).name,a=()=>K(n).value;var o=kc();let s;var c=z(o),l=V(c,!0),u=H(c,2),d=z(u);let f;P(u);var p=V(H(u,2));P(o),U((e,n,a)=>{s=li(o,1,`report-row svelte-1fbui7h`,null,s,{unknown:e,alternate:r%2==1}),Q(c,`title`,i()),X(l,i()),f=di(d,``,f,{width:n}),X(p,`${a??``}${t.mode===`periods`?`%`:``}`)},[()=>i().trim().toLowerCase()===`unknown`,()=>`${Math.max(0,100*a()/K(h))}%`,()=>g(a())]),Y(e,o)}),P(n),U(()=>X(i,t.mode===`breakdown`?K(u):`QUOTA USED`)),Y(e,n)},ae=e=>{Y(e,jc())},oe=e=>{Y(e,Mc())};Z(re,e=>{K(f).length?e(ie):t.mode===`breakdown`&&K(d)?.knownEmpty?e(ae,1):(K(i)&&t.mode===`breakdown`||K(a)&&t.mode===`periods`)&&e(oe,2)}),P(w),U(()=>{x.disabled=K(s)>=K(o)-1,S.disabled=K(s)===0,X(C,K(o)?`${K(s)+1} / ${K(o)}`:`NO DATA`),X(E,t.mode===`breakdown`?`DAILY BREAKDOWN`:`QUOTA WINDOWS`),X(ee,`${(t.mode===`breakdown`?t.reports?.dailyStatus||`UNAVAILABLE`:t.reports?.planStatus||`UNAVAILABLE`)??``} // UTC`)}),gi(b,()=>K(n),e=>R(n,e)),q(`click`,x,()=>R(r,K(s)+1)),q(`click`,S,()=>R(r,K(s)-1)),Y(e,_),We()}Sr([`click`]);var Fc=864e5;function Ic(e,t){let n=new Date(e);n.setUTCDate(1),n.setUTCMonth(n.getUTCMonth()+t);let r=new Date(Date.UTC(n.getUTCFullYear(),n.getUTCMonth()+1,0)).getUTCDate();return n.setUTCDate(Math.min(e.getUTCDate(),r)),n}function Lc(e,t,n){let r=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()));for(let e=0;;e++){let i=Ic(r,-t),a=new Date(i.getTime()+Fc);if(e===n)return{start:a,end:r};r=i}}var Rc=J(``),zc=J(``,1),Bc=J(`

      History refresh failed. Any displayed history is the last successful + observation.

      `),Vc=J(`

      Token history unavailable; other reports may still be available.

      `),Hc=J(``),Uc=J(`
      `),Wc=J(`

      LESS ░ ▒ ▓ █ MORE // HOVER FOR DATE AND TOKENS

      `,1),Gc=J(`
      `,1),Kc=J(` `),qc=J(`

      LIFETIME TOKENS

      PEAK DAY

      CURRENT STREAK

      DAYS

      LONGEST TURN

      SECONDS

      LONGEST STREAK

      DAYS

      OPENAI // UTC

      Accessible data table
      Date (UTC)Tokens

      `,1),Jc=J(`

      History unavailable or awaiting a matching account observation. Missing + history is not treated as zero usage.

      `),Yc=J(`

      USAGE // ACCOUNT HISTORY

      Account-wide history reported by Codex, not the local Sessions counter. Dates + use UTC. Allowance history is approximate where indicated; reset causes are + not provided.

      `,1);function Xc(e,t){Ue(t,!0);let n=L(`daily`),r=L(12),i=L(0),a=F(()=>{let e=$.data?.usage?.dailyUsageBuckets;if(!e)return[];let{start:t,end:n}=Lc(new Date,K(r),K(i)),a=new Map;for(let t of e)!Number.isFinite(t.tokens)||t.tokens<0||a.set(t.startDate,(a.get(t.startDate)||0)+t.tokens);let o=[];for(let e=t;e<=n;e=new Date(e.getTime()+864e5)){let t=e.toISOString().slice(0,10);o.push({date:t,tokens:a.get(t)||0})}return o}),o=F(()=>Math.max(1,...K(a).map(e=>e.tokens))),s=F(()=>K(a).length?new Date(K(a)[0].date+`T00:00:00Z`).getUTCDay():0),c=F(()=>{if(K(n)===`monthly`){let e=new Map;for(let t of K(a))e.set(t.date.slice(0,7),(e.get(t.date.slice(0,7))||0)+t.tokens);return[...e].map(([e,t])=>({date:e,tokens:t}))}let e=0;return K(a).map(t=>({date:t.date,tokens:e+=t.tokens}))});var l=Yc(),u=H(B(l),4);Xr(u,20,()=>[[`daily`,`Daily`],[`monthly`,`Monthly`],[`cumulative`,`Cumulative`],[`breakdown`,`Breakdown`],[`periods`,`Windows`]],Kr,(e,t)=>{var r=F(()=>_(t,2));let a=()=>K(r)[0],o=()=>K(r)[1];var s=Rc();let c;var l=V(s,!0);U(()=>{Q(s,`aria-pressed`,K(n)===a()),c=li(s,1,``,null,c,{active:K(n)===a()}),X(l,o())}),q(`click`,s,()=>{R(n,a(),!0),R(i,0)}),Y(e,s)}),P(u);var d=H(u,2),f=z(d),p=e=>{var t=zc(),n=B(t),a=H(z(n)),o=z(a);o.value=o.__value=6;var s=H(o);s.value=s.__value=12,P(a),hi(a),P(n);var c=H(n),l=H(c);U(()=>l.disabled=K(i)===0),q(`change`,a,()=>R(i,0)),gi(a,()=>K(r),e=>R(r,e)),q(`click`,c,()=>Kt(i)),q(`click`,l,()=>Kt(i,-1)),Y(e,t)};Z(f,e=>{K(n)!==`breakdown`&&K(n)!==`periods`&&e(p)}),P(d);var m=H(d,2),h=e=>{Y(e,Bc())};Z(m,e=>{$.data?.usageError&&e(h)});var g=H(m,2),v=e=>{Y(e,Vc())};Z(g,e=>{$.data?.usage?.tokenStatus===`UNAVAILABLE`&&K(n)!==`breakdown`&&K(n)!==`periods`&&e(v)});var y=H(g,2),b=e=>{var t=Nr();Gr(B(t),()=>K(n),e=>{{let t=F(()=>$.data?.usage?.reports);Pc(e,{get reports(){return K(t)},get mode(){return K(n)}})}}),Y(e,t)},x=e=>{var t=qc(),r=B(t),i=z(r),l=V(H(z(i),2),!0);P(i);var u=H(i,2),d=V(H(z(u),2),!0);P(u);var f=H(u,2),p=H(z(f),2),m=z(p);xe(),P(p),P(f);var h=H(f,2),g=H(z(h),2),_=z(g);xe(),P(g),P(h);var v=H(h,2),y=H(z(v),2),b=z(y);xe(),P(y),P(v),P(r);var x=H(r,4),S=z(x),C=V(S),w=H(S,2),T=e=>{var t=Wc(),n=B(t),r=z(n),i=z(r);Xr(i,17,()=>Array(K(s)),Kr,(e,t)=>{Y(e,Hc())}),Xr(H(i,2),17,()=>K(a),Kr,(e,t)=>{var n=Uc();let r,i;U(e=>{r=li(n,1,`heat-cell`,null,r,{zero:K(t).tokens===0}),Q(n,`title`,e),i=di(n,``,i,{opacity:K(t).tokens?.25+.75*K(t).tokens/K(o):1})},[()=>`${K(t).date}: ${ma(K(t).tokens)} tokens`]),Y(e,n)}),P(r),P(n),xe(2),Y(e,t)},E=e=>{var t=Gc(),r=B(t);{let e=F(()=>K(c).map(e=>e.tokens)),t=F(()=>K(n)+` usage`);ao(r,{get values(){return K(e)},get label(){return K(t)}})}var i=H(r,2),a=z(i),o=V(a,!0),s=V(H(a),!0);P(i),U(e=>{X(o,K(c)[0]?.date),X(s,e)},[()=>K(c).at(-1)?.date]),Y(e,t)};Z(w,e=>{K(n)===`daily`?e(T):e(E,-1)});var D=H(w,2),ee=H(z(D),2),te=z(ee),O=H(z(te));Xr(O,21,()=>K(n)===`daily`?K(a):K(c),Kr,(e,t)=>{var n=Kc(),r=z(n),i=V(r,!0),a=V(H(r),!0);P(n),U(e=>{X(i,K(t).date),X(a,e)},[()=>ma(K(t).tokens)]),Y(e,n)}),P(O),P(te),P(ee),P(D),P(x);var ne=V(H(x,2));U((e,t,n,r,i,o,s)=>{X(l,e),X(d,t),X(m,`${n??``} `),X(_,`${r??``} `),X(b,`${i??``} `),X(C,`${K(a)[0]?.date??``} — ${o??``}`),X(ne,`OBSERVED ${s??``} // CUMULATIVE IS FOR THE SELECTED PERIOD`)},[()=>ma($.data.usage.summary.lifetimeTokens),()=>ma($.data.usage.summary.peakDailyTokens),()=>ma($.data.usage.summary.currentStreakDays),()=>ma($.data.usage.summary.longestRunningTurnSec),()=>ma($.data.usage.summary.longestStreakDays),()=>K(a).at(-1)?.date,()=>ha($.data.usageAt)]),Y(e,t)},S=e=>{Y(e,Jc())};Z(y,e=>{K(n)===`breakdown`||K(n)===`periods`?e(b):$.data?.usage&&$.data.usage.dailyUsageBuckets!==null?e(x,1):e(S,-1)}),Y(e,l),We()}Sr([`click`,`change`]);var Zc=J(`

      Page not found

      Return to Quota

      `,1);function Qc(e){var t=Zc();xe(2),Y(e,t)}var $c=`CODEXOMETER`,el=[`█▀▀ █▀█ █▀▄ █▀▀ ▀▄▀ █▀█ █▀▄▀█ █▀▀ ▀█▀ █▀▀ █▀█`,`█▄▄ █▄█ █▄▀ ██▄ █ █ █▄█ █ ▀ █ ██▄ █ ██▄ █▀▄`].flatMap(e=>[[...e].map(e=>e===`█`||e===`▀`),[...e].map(e=>e===`█`||e===`▄`)]),tl=el[0].length,nl=el.length,rl=0,il=[...$c].map(e=>{let t=e===`M`?5:3,n={start:rl,width:t};return rl+=t+1,n}),al=`ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`,ol=`010 101 111 101.110 111 101 110.111 100 100 111.110 101 101 110.111 100 110 111.111 100 110 100.111 100 101 111.101 101 111 101.111 010 010 111.001 001 101 111.101 110 110 101.100 100 100 111.101 111 101 101.101 111 111 101.111 101 101 111.111 101 111 100.111 101 111 001.111 101 110 101.111 100 001 111.111 010 010 010.101 101 101 111.101 101 101 010.101 101 111 101.101 010 010 101.101 101 010 010.111 001 100 111.111 101 101 111.110 010 010 111.111 001 110 111.111 011 001 111.101 101 111 001.111 110 001 111.100 111 101 111.111 001 010 010.111 111 101 111.111 101 111 001`.split(`.`).map(e=>e.split(` `));function sl(e=$c,t=-1,n=!1){let r=``;for(let i=0;i{e|=0,e=e+1831565813|0;let t=Math.imul(e^e>>>15,1|e);return t^=t+Math.imul(t^t>>>7,61|t),((t^t>>>14)>>>0)/4294967296}}function dl(){let e=cl[Math.floor(Math.random()*cl.length)],t=Math.floor(Math.random()*4294967296),n=ul(t),r=[...$c].map((e,t)=>t);for(let e=r.length-1;e>0;e--){let t=Math.floor(n()*(e+1));[r[e],r[t]]=[r[t],r[e]]}return{variant:e,seed:t,order:r}}function fl(e,t){if(e.variant===`typing`){let e=t<1080,n=e?0:Math.min(Math.floor((t-1080)/90)+1,11);return{text:$c.slice(0,n).padEnd(11),cursor:n,dot:e?Math.floor(t/180)%2==0:n<11}}if(e.variant===`shuffle`){let n=ul(e.seed^Math.floor(t/70)),r=[...$c].map(e=>{let t;do t=al[Math.floor(n()*36)];while(t===e);return t}),i=Math.min(Math.floor(t/110),11);for(let t of e.order.slice(0,i))r[t]=$c[t];return{text:r.join(``),cursor:-1,dot:!1}}return{text:$c,cursor:-1,dot:!1}}var pl=jr(``);function ml(e,t){Ue(t,!0);let n=Gi(t,`text`,3,$c),r=Gi(t,`cursor`,19,()=>-1),i=Gi(t,`dot`,3,!1),a=F(()=>sl(n(),r(),i()));var o=pl(),s=V(o);U(()=>{Q(o,`viewBox`,`0 0 ${tl} ${nl}`),Q(o,`width`,tl),Q(o,`height`,nl),Q(s,`d`,K(a))}),Y(e,o),We()}var hl=J(`
      `),gl=J(`
      `);function _l(e,t){Ue(t,!0);let n=L(void 0),r=L(0),i=L(Yt({width:0,height:0,pixelRatio:1})),a=L(Yt({x:0,y:0,width:0})),o=F(()=>K(n)?fl(K(n),K(r)):void 0),s=F(()=>{if(!K(n))return{x:0,y:0,width:0};let e=ll[K(n).variant],t=Math.min(Math.max((K(r)-e-180)/800,0),1),o=t*t*(3-2*t),s=K(i).width+(K(a).width-K(i).width)*o,c=Math.max((K(i).height-K(i).width*nl/tl)/2,0),l=(K(n).variant===`slide`&&K(r)Math.round(e*K(i).pixelRatio)/K(i).pixelRatio;return{x:d(l),y:d(u),width:d(s)}});Ki(()=>{let e=matchMedia(`(prefers-reduced-motion: reduce)`);if(e.matches){t.onfinish();return}R(n,dl(),!0);let o=ll[K(n).variant]+180+800,s=0,c=!1,l=performance.now(),u=()=>{let e=t.target?.getBoundingClientRect();R(i,{width:window.innerWidth,height:window.innerHeight,pixelRatio:window.devicePixelRatio||1},!0),e&&R(a,{x:e.x,y:e.y,width:e.width},!0)},d=()=>{c||(c=!0,cancelAnimationFrame(s),t.onfinish())},f=e=>{e instanceof PointerEvent&&e.button!==0||e instanceof KeyboardEvent&&(e.metaKey||e.ctrlKey||e.altKey||e.shiftKey)||e instanceof KeyboardEvent&&[`Shift`,`Control`,`Alt`,`Meta`].includes(e.key)||(e.preventDefault(),e.stopImmediatePropagation(),d())},p=()=>{e.matches&&d()},m=e=>{R(r,Math.max(e-l,0),!0),K(r)>=o?d():s=requestAnimationFrame(m)},h=new ResizeObserver(u);if(t.target){h.observe(t.target),t.target.parentElement&&h.observe(t.target.parentElement);let e=t.target.closest(`header`);e&&h.observe(e)}return u(),window.addEventListener(`resize`,u),document.addEventListener(`keydown`,f,!0),document.addEventListener(`pointerdown`,f,!0),e.addEventListener(`change`,p),s=requestAnimationFrame(m),()=>{c=!0,cancelAnimationFrame(s),h.disconnect(),window.removeEventListener(`resize`,u),document.removeEventListener(`keydown`,f,!0),document.removeEventListener(`pointerdown`,f,!0),e.removeEventListener(`change`,p)}});var c=gl(),l=z(c),u=e=>{var t=hl();let n;ml(z(t),{get text(){return K(o).text},get cursor(){return K(o).cursor},get dot(){return K(o).dot}}),P(t),U(()=>{Q(t,`data-text`,K(o).text),Q(t,`data-cursor`,K(o).dot?K(o).cursor:void 0),n=di(t,``,n,{left:`${K(s).x}px`,top:`${K(s).y}px`,width:`${K(s).width}px`})}),Y(e,t)};Z(l,e=>{K(o)&&e(u)});var d=H(l,2);P(c),U(()=>Q(c,`data-entrance`,K(n)?.variant)),q(`click`,d,function(...e){t.onfinish?.apply(this,e)}),Y(e,c),We()}Sr([`click`]);var vl=J(` `),yl=J(`

      `),bl=J(`

      Connecting to your local Codexometer…

      `),xl=J(``),Sl=J(`

      Your quota. Your sessions. Your command centre.

      `);function Cl(e,t){Ue(t,!0);let n={"/":no,"/quota/:view?":no,"/sessions/:id?":bc,"/usage":Xc,"*":Qc},r={quota:/^(?:\/|\/quota(?:\/[^/]+)?\/?)$/,sessions:/^\/sessions(?:\/[^/]+)?\/?$/,usage:/^\/usage\/?$/},i=L(`hacker`),a=L(!0),o=L(void 0);vn(()=>{ca.requests&&lr().then(()=>{K(o)?.focus(),K(o)?.scrollIntoView({block:`nearest`})})});let s=L(void 0),c=[`hacker`,`rust`,`blue-steel`,`ultraviolet`,`nightshade`];vn(()=>{la()}),vn(()=>{if($.data)for(let[e,t]of Object.entries(r))t.test(Xi.location)&&(oa.tab=e)}),Ki(()=>{try{let e=localStorage.getItem(`codexometer.web.theme`);e&&c.includes(e)&&R(i,e,!0)}catch{}return ba()});function l(){try{localStorage.setItem(`codexometer.web.theme`,K(i))}catch{}}var u=Sl(),d=z(u),f=z(d),p=z(f);ml(z(p),{}),P(p),Li(p,e=>R(s,e),()=>K(s)),xe(2),P(f);var m=H(f,2),h=z(m);let g;var v=H(h,1,!0),y=V(H(v));P(m),P(d);var b=H(d,2);Xr(b,21,()=>Object.entries(r),Kr,(e,t)=>{var n=F(()=>_(K(t),2));let r=()=>K(n)[0],i=()=>K(n)[1],a=F(()=>i().test(Xi.location));var o=vl();let s;var c=V(o,!0);U(e=>{Q(o,`href`,r()===`quota`?`#/quota/`+oa.view:`#/`+r()),Q(o,`aria-current`,K(a)?`page`:void 0),s=li(o,1,``,null,s,{active:K(a)}),X(c,e)},[()=>r().toUpperCase()]),Y(e,o)}),P(b);var x=H(b,2),S=z(x),C=e=>{var t=yl(),n=V(t,!0);U(()=>X(n,$.error)),Y(e,t)};Z(S,e=>{$.error&&e(C)});var w=H(S,2),T=e=>{ea(e,{get routes(){return n}})},E=e=>{Y(e,bl())};Z(w,e=>{$.data?e(T):$.error||e(E,1)}),P(x);var D=H(x,2),ee=z(D),te=V(ee),O=H(ee,2),ne=V(O,!0),k=H(O,2),re=H(z(k));Xr(re,21,()=>c,Kr,(e,t)=>{var n=xl(),r=V(n,!0),i={};U(e=>{X(r,e),i!==(i=K(t))&&(n.value=(n.__value=i)??``)},[()=>K(t).replace(`-`,` `).toUpperCase()]),Y(e,n)}),P(re),Li(re,e=>R(o,e),()=>K(o)),hi(re),P(k),P(D);var ie=H(D,2),ae=e=>{_l(e,{get target(){return K(s)},onfinish:()=>R(a,!1)})};Z(ie,e=>{K(a)&&e(ae)}),P(u),U(()=>{Q(u,`data-theme`,K(i)),Q(u,`data-starting`,K(a)),d.inert=K(a),g=li(h,1,`lamp`,null,g,{lit:$.connected}),X(v,$.connected?`CONNECTED`:`OFFLINE`),X(y,`EXPERIMENTAL // ${$.data?.control?`SESSION CONTROL`:`READ ONLY`}`),b.inert=K(a),x.inert=K(a),D.inert=K(a),X(te,`v${($.data?.version||`…`)??``} // LOCAL ONLY`),X(ne,$.data?.control?`Session control enabled. Stop the server to revoke access.`:`No actions can be sent from this preview.`)}),q(`change`,re,l),gi(re,()=>K(i),e=>R(i,e)),Y(e,u),We()}Sr([`change`]),zr(Cl,{target:document.getElementById(`app`)}); \ No newline at end of file diff --git a/internal/web/dist/assets/index-r_yfxbaP.js b/internal/web/dist/assets/index-r_yfxbaP.js deleted file mode 100644 index ed599815..00000000 --- a/internal/web/dist/assets/index-r_yfxbaP.js +++ /dev/null @@ -1,50 +0,0 @@ -(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=Array.isArray,t=Array.prototype.indexOf,n=Array.prototype.includes,r=Array.from,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyDescriptors,s=Object.prototype,c=Array.prototype,l=Object.getPrototypeOf,u=Object.isExtensible;function d(e){return typeof e==`function`}var f=()=>{};function p(e){return e()}function m(e){for(var t=0;t{e=n,t=r}),resolve:e,reject:t}}function g(e,t,n=!1){return e===void 0?n?t():t:e}function _(e,t){if(Array.isArray(e))return e;if(t===void 0||!(Symbol.iterator in e))return Array.from(e);let n=[];for(let r of e)if(n.push(r),n.length===t)break;return n}var v=1024,y=2048,b=4096,x=8192,S=16384,C=32768,w=1<<25,T=65536,E=1<<19,D=1<<20,ee=1<<25,te=1<<21,O=1<<22,ne=1<<23,k=Symbol(`$state`),re=Symbol(`component`),ie=Symbol(`legacy props`),ae=Symbol(``),oe=Symbol(`attributes`),se=Symbol(`class`),ce=Symbol(`style`),le=Symbol(`text`),ue=Symbol(`form reset`),de=new class extends Error{name=`StaleReactionError`;message="The reaction that called `getAbortSignal()` was re-run or destroyed"},fe=!!globalThis.document?.contentType&&globalThis.document.contentType.includes(`xml`),pe={},A=Symbol(`uninitialized`),me=`http://www.w3.org/1999/xhtml`;function he(){console.warn(`https://svelte.dev/e/derived_inert`)}function ge(e){console.warn(`https://svelte.dev/e/hydration_mismatch`)}function _e(){console.warn(`https://svelte.dev/e/select_multiple_invalid_value`)}function ve(){console.warn(`https://svelte.dev/e/svelte_boundary_reset_noop`)}var j=!1;function ye(e){j=e}var M;function be(e){if(e===null)throw ge(),pe;return M=e}function xe(){return be(sn(M))}function N(e){if(j){if(sn(M)!==null)throw ge(),pe;M=e}}function Se(e=1){if(j){for(var t=e,n=M;t--;)n=sn(n);M=n}}function Ce(e=!0){for(var t=0,n=M;;){if(n.nodeType===8){var r=n.data;if(r===`]`){if(t===0)return n;--t}else(r===`[`||r===`[!`||r[0]===`[`&&!isNaN(Number(r.slice(1))))&&(t+=1)}var i=sn(n);e&&n.remove(),n=i}}function we(e){if(!e||e.nodeType!==8)throw ge(),pe;return e.data}function Te(e){return e===this.v}function Ee(e,t){return e==e?e!==t||typeof e==`object`&&!!e||typeof e==`function`:t==t}function De(e){return!Ee(e,this.v)}function Oe(e){throw Error(`https://svelte.dev/e/lifecycle_outside_component`)}function ke(){throw Error(`https://svelte.dev/e/async_derived_orphan`)}function Ae(e,t,n){throw Error(`https://svelte.dev/e/each_key_duplicate`)}function je(e){throw Error(`https://svelte.dev/e/effect_in_teardown`)}function Me(){throw Error(`https://svelte.dev/e/effect_in_unowned_derived`)}function Ne(e){throw Error(`https://svelte.dev/e/effect_orphan`)}function Pe(){throw Error(`https://svelte.dev/e/effect_update_depth_exceeded`)}function Fe(e){throw Error(`https://svelte.dev/e/props_invalid_value`)}function Ie(){throw Error(`https://svelte.dev/e/state_descriptors_fixed`)}function Le(){throw Error(`https://svelte.dev/e/state_prototype_fixed`)}function Re(){throw Error(`https://svelte.dev/e/state_unsafe_mutation`)}function ze(){throw Error(`https://svelte.dev/e/svelte_boundary_reset_onerror`)}var Be=!1;function Ve(){Be=!0}var He=null;function Ue(e){He=e}function We(e,t=!1,n){He={p:He,i:!1,c:null,e:null,s:e,x:null,r:W,l:Be&&!t?{s:null,u:null,$:[]}:null}}function Ge(e){var t=He,n=t.e;if(n!==null){t.e=null;for(var r of n)bn(r)}return e!==void 0&&(t.x=e),t.i=!0,He=t.p,Ke(e)}function Ke(e={}){return i(e,re,{value:!0}),e}function qe(){return!Be||He!==null&&He.l===null}var Je=[];function Ye(){var e=Je;Je=[],m(e)}function Xe(e){if(Je.length===0&&!wt){var t=Je;queueMicrotask(()=>{t===Je&&Ye()})}Je.push(e)}function Ze(){for(;Je.length>0;)Ye()}var Qe=~(y|b|v);function $e(e,t){e.f=e.f&Qe|t}function et(e){e.f&512||e.deps===null?$e(e,v):$e(e,b)}function tt(e,t,n){e.f&2048?t.add(e):e.f&4096&&n.add(e),$e(e,v)}function nt(e){j&&on(e)!==null&&cn(e)}var rt=!1;function it(){rt||(rt=!0,document.addEventListener(`reset`,e=>{Promise.resolve().then(()=>{if(!e.defaultPrevented)for(let t of e.target.elements)t[ue]?.()})},{capture:!0}))}function at(e){var t=U,n=W;Wn(null),Gn(null);try{return e()}finally{Wn(t),Gn(n)}}function ot(e,t,n,r=n){e.addEventListener(t,()=>at(n));let i=e[ue];e[ue]=i?()=>{i(),r(!0)}:()=>r(!0),it()}function st(e,t,n,r){let i=qe()?dt:mt;var a=e.filter(e=>!e.settled),o=t.map(i);if(n.length===0&&a.length===0){r(o);return}var s=W,c=ct(),l=a.length===1?a[0].promise:a.length>1?Promise.all(a.map(e=>e.promise)):null;function u(e){if(!(s.f&16384)){c();try{r([...o,...e])}catch(e){pn(e,s)}lt()}}var d=ut();if(n.length===0){l.then(()=>u([])).finally(d);return}function f(){Promise.all(n.map(e=>pt(e))).then(u).catch(e=>pn(e,s)).finally(d)}l?l.then(()=>{c(),f(),lt()}):f()}function ct(){var e=W,t=U,n=He,r=F;return function(i=!0){Gn(e),Wn(t),Ue(n),i&&!(e.f&16384)&&(r?.activate(),r?.apply())}}function lt(e=!0){Gn(null),Wn(null),Ue(null),e&&F?.deactivate()}function ut(){var e=W,t=e.b,n=F,r=!!t?.is_rendered();return t?.update_pending_count(1,n),n.increment(r,e),()=>{t?.update_pending_count(-1,n),n.decrement(r,e)}}function dt(e){var t=2|y;return W!==null&&(W.f|=E),{ctx:He,deps:null,effects:null,equals:Te,f:t,fn:e,reactions:null,rv:0,v:A,wv:0,parent:W,ac:null}}var ft=Symbol(`obsolete`);function pt(e,t,n){let r=W;r===null&&ke();var i=void 0,a=Vt(A),o=!U,s=new Set;return wn(()=>{var t=W,n=h();i=n.promise;try{Promise.resolve(e()).then(n.resolve,e=>{e!==de&&n.reject(e)}).finally(lt)}catch(e){n.reject(e),lt()}var c=F;if(o){if(t.f&32768)var l=ut();if(r.b?.is_rendered())c.async_deriveds.get(t)?.reject(ft);else for(let e of s.values())e.reject(ft);s.add(n),c.async_deriveds.set(t,n)}let u=(e,t=void 0)=>{l?.(),s.delete(n),t!==ft&&(c.activate(),t?(a.f|=ne,Gt(a,t)):(a.f&8388608&&(a.f^=ne),Gt(a,e)),c.deactivate())};n.promise.then(u,e=>u(null,e||`unknown`))}),vn(()=>{for(let e of s)e.reject(ft)}),new Promise(e=>{function t(n){function r(){n===i?e(a):t(i)}n.then(r,r)}t(i)})}function P(e){let t=dt(e);return qn(t),t}function mt(e){let t=dt(e);return t.equals=De,t}function ht(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;n{t.ac.abort(de),t.ac=null}),t.fn!==null&&(t.teardown=f),cr(t,0),kn(t))}function yt(e){if(e.effects!==null)for(let t of e.effects)t.teardown&&t.fn!==null&&lr(t)}var bt=null,F=null,xt=null,St=null,Ct=null,wt=!1,Tt=!1,Et=null,Dt=null,Ot=0,kt=1,At=class e{id=kt++;#e=!1;linked=!0;#t=null;#n=null;async_deriveds=new Map;current=new Map;previous=new Map;#r=new Set;#i=new Set;#a=0;#o=new Map;#s=null;#c=[];#l=[];#u=new Set;#d=new Set;#f=new Map;#p=new Set;is_fork=!1;#m=!1;constructor(){bt===null?bt=this:(bt.#n=this,this.#t=bt),bt=this}#h(){if(this.is_fork)return!0;for(let n of this.#o.keys()){for(var e=n,t=!1;e.parent!==null;){if(this.#f.has(e)){t=!0;break}e=e.parent}if(!t)return!0}return!1}skip_effect(e){this.#f.has(e)||this.#f.set(e,{d:[],m:[]}),this.#p.delete(e)}unskip_effect(e,t=e=>this.schedule(e)){var n=this.#f.get(e);if(n){this.#f.delete(e);for(var r of n.d)$e(r,y),t(r);for(r of n.m)$e(r,b),t(r)}this.#p.add(e)}#g(){var e=[];for(let i of this.#c)if(!(i.f&16384||!(i.f&6144))){for(var t=i,n=!1;t.parent!==null;){t=t.parent;var r=t.f;if(r&96){if(!(r&1024)){n=!0;break}t.f^=v}}n||e.push(t)}return this.#c=[],e}#_(){this.#e=!0;for(let e of this.#u)this.#d.delete(e),$e(e,y),this.schedule(e);for(let e of this.#d)$e(e,b),this.schedule(e);this.apply();for(var t=Et=[],n=[],r=Dt=[];this.#c.length>0;){Ot++>1e3&&(this.#S(),Mt());for(let e of this.#g())try{this.#v(e,t,n)}catch(t){throw Lt(e),this.#h()||this.discard(),t}}if(F=null,r.length>0){var i=e.ensure();for(let e of r)i.schedule(e)}if(Et=null,Dt=null,this.#h()){this.#x(n),this.#x(t);for(let[e,t]of this.#f)It(e,t);r.length>0&&F.#_();return}let a=this.#y();if(a)this.#x(n),this.#x(t),a.#b(this);else{this.#u.clear(),this.#d.clear();for(let e of this.#r)e(this);this.#r.clear(),xt=this,Pt(n),Pt(t),xt=null,this.#s?.resolve();var o=F;if(this.#a===0&&(this.#c.length===0||o!==null)&&this.#S(),this.#c.length>0){if(o!==null){for(let e of this.#c)o.#c.push(e);this.#c=[]}else o=this}o!==null&&(zt.clear(),o.#_())}}#v(e,t,n){e.f^=v;for(var r=e.first;r!==null;){var i=r.f,a=!!(i&96);if(!(a&&i&1024||i&8192||this.#f.has(r))&&r.fn!==null){a?r.f^=v:i&4?t.push(r):rr(r)&&(i&16&&this.#d.add(r),lr(r));var o=r.first;if(o!==null){r=o;continue}}for(;r!==null;){var s=r.next;if(s!==null){r=s;break}r=r.parent}}}#y(){for(var e=this.#t;e!==null;){if(!e.is_fork){for(let[t,[,n]]of this.current)if(e.current.has(t)&&!n)return e}e=e.#t}return null}#b(e){for(let[t,n]of e.current)!this.previous.has(t)&&e.previous.has(t)&&this.previous.set(t,e.previous.get(t)),this.current.set(t,n);for(let[t,n]of e.async_deriveds){let e=this.async_deriveds.get(t);e&&n.promise.then(e.resolve).catch(e.reject)}e.async_deriveds.clear(),this.transfer_effects(e.#u,e.#d);let t=e=>{var n=e.reactions;if(n!==null&&!(e.f&2&&!(e.f&6144)))for(let e of n){var r=e.f;if(r&2)t(e);else{var i=e;r&4194320&&!this.async_deriveds.has(i)&&(this.#d.delete(i),$e(i,y),this.schedule(i))}}};for(let e of this.current.keys())t(e);this.oncommit(()=>e.discard()),e.#S(),F=this,this.#_()}#x(e){for(var t=0;t{this.#m=!1,this.linked&&this.flush()}))}transfer_effects(e,t){for(let t of e)this.#u.add(t);for(let e of t)this.#d.add(e);e.clear(),t.clear()}oncommit(e){this.#r.add(e)}ondiscard(e){this.#i.add(e)}settled(){return(this.#s??=h()).promise}static ensure(){if(F===null){let t=F=new e;!Tt&&!wt&&Xe(()=>{t.#e||t.flush()})}return F}apply(){St=null}schedule(e){Ct=e,e.b?.is_pending&&e.f&16777228&&!(e.f&32768)?e.b.defer_effect(e):this.#c.push(e)}#S(){if(this.linked){var e=this.#t,t=this.#n;e===null||(e.#n=t),t===null?bt=e:t.#t=e,this.linked=!1}}};function jt(e){var t=wt;wt=!0;try{var n;for(e&&(F!==null&&!F.is_fork&&F.flush(),n=e());;){if(Ze(),F===null)return n;F.flush()}}finally{wt=t}}function Mt(){try{Pe()}catch(e){pn(e,Ct)}}var Nt=null;function Pt(e){var t=e.length;if(t!==0){for(var n=0;n0)){zt.clear();for(let e of Nt){if(e.f&24576)continue;let t=[e],n=e.parent;for(;n!==null;)Nt.has(n)&&(Nt.delete(n),t.push(n)),n=n.parent;for(let e=t.length-1;e>=0;e--){let n=t[e];n.f&24576||lr(n)}}Nt.clear()}}Nt=null}}function Ft(e){F.schedule(e)}function It(e,t){if(!(e.f&32&&e.f&1024)){e.f&2048?t.d.push(e):e.f&4096&&t.m.push(e),$e(e,v);for(var n=e.first;n!==null;)It(n,t),n=n.next}}function Lt(e){$e(e,v);for(var t=e.first;t!==null;)Lt(t),t=t.next}var Rt=new Set,zt=new Map,Bt=!1;function Vt(e,t){return{f:0,v:e,reactions:null,equals:Te,rv:0,wv:0}}function I(e,t){let n=Vt(e,t);return qn(n),n}function Ht(e,t=!1,n=!0){let r=Vt(e);return t||(r.equals=De),Be&&n&&He!==null&&He.l!==null&&(He.l.s??=[]).push(r),r}function L(e,t,n=!1){return U!==null&&(!Un||U.f&131072)&&qe()&&U.f&4325394&&(Kn===null||!Kn.has(e))&&Re(),Gt(e,n?Xt(t):t,Dt)}var Ut=null,Wt=0;function Gt(e,t,n=null){if(!e.equals(t)){Vn?zt.set(e,t):zt.has(e)||zt.set(e,e.v);var r=At.ensure();if(r.capture(e,t),e.f&2){let t=e;e.f&2048&>(t),St===null&&et(t)}e.wv=nr(),Ut=null,Wt=0,Yt(e,y,n),Ut=null,qe()&&W!==null&&W.f&1024&&!(W.f&96)&&(Xn===null?Zn([e]):Xn.push(e)),!r.is_fork&&Rt.size>0&&!Bt&&Kt()}return t}function Kt(){Bt=!1;for(let e of Rt){e.f&1024&&$e(e,b);let t;try{t=rr(e)}catch{t=!0}t&&lr(e)}Rt.clear()}function qt(e,t=1){var n=G(e),r=t===1?n++:n--;return L(e,n),r}function Jt(e){L(e,e.v+1)}function Yt(e,t,n){var r=e.reactions;if(r!==null){var i=qe(),a=r.length;if(Wt+=a,Wt>1e5&&Ut===null&&(Ut=new Set),Ut!==null){if(Ut.has(e))return;Ut.add(e)}for(var o=0;o{if(er===d)return e();var t=U,n=er;Wn(null),tr(d);var r=e();return Wn(t),tr(n),r};return i&&r.set(`length`,I(t.length,u)),new Proxy(t,{defineProperty(e,t,n){`value`in n&&n.configurable!==!1&&n.enumerable!==!1&&n.writable!==!1||Ie();var i=r.get(t);return i===void 0?f(()=>{var e=I(n.value,u);return r.set(t,e),e}):L(i,n.value,!0),!0},deleteProperty(e,t){var n=r.get(t);if(n===void 0){if(t in e){let e=f(()=>I(A,u));r.set(t,e),Jt(o)}}else L(n,A),Jt(o);return!0},get(e,n,i){if(n===k)return t;var o=r.get(n),s=n in e;if(o===void 0&&(!s||a(e,n)?.writable)&&(o=f(()=>I(Xt(s?e[n]:A),u)),r.set(n,o)),o!==void 0){var c=G(o);return c===A?void 0:c}return Reflect.get(e,n,i)},getOwnPropertyDescriptor(e,t){this.has?.(e,t);var n=Reflect.getOwnPropertyDescriptor(e,t),i=r.get(t);if(i!==void 0){var a=G(i);if(a===A)return;if(n&&`value`in n)n.value=a;else return{enumerable:!0,configurable:!0,value:a,writable:!0}}return n},has(e,t){if(t===k)return!0;var n=r.get(t),i=n!==void 0&&n.v!==A||Reflect.has(e,t);return(n!==void 0||W!==null&&(!i||a(e,t)?.writable))&&(n===void 0&&(n=f(()=>I(i?Xt(e[t]):A,u)),r.set(t,n)),G(n)===A)?!1:i},set(e,t,n,s){var c=r.get(t),l=t in e;if(i&&t===`length`)for(var d=n;dI(A,u)),r.set(d+``,p)):L(p,A)}if(c===void 0)(!l||a(e,t)?.writable)&&(c=f(()=>I(void 0,u)),L(c,Xt(n)),r.set(t,c));else{l=c.v!==A;var m=f(()=>Xt(n));L(c,m)}var h=Reflect.getOwnPropertyDescriptor(e,t);if(h?.set&&h.set.call(s,n),!l){if(i&&typeof t==`string`){var g=r.get(`length`),_=Number(t);Number.isInteger(_)&&_>=g.v&&L(g,_+1)}Jt(o)}return!0},ownKeys(e){G(o);var t=Reflect.ownKeys(e).filter(e=>{var t=r.get(e);return t===void 0||t.v!==A});for(var[n,i]of r)i.v!==A&&!(n in e)&&t.push(n);return t},setPrototypeOf(){Le()}})}function Zt(e){try{if(typeof e==`object`&&e&&k in e)return e[k]}catch{}return e}function Qt(e,t){return Object.is(Zt(e),Zt(t))}var $t,en,tn,nn;function rn(){if($t===void 0){$t=window,en=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,n=Text.prototype;tn=a(t,`firstChild`).get,nn=a(t,`nextSibling`).get,u(e)&&(e[se]=void 0,e[oe]=null,e[ce]=void 0,e.__e=void 0),u(n)&&(n[le]=void 0)}}function an(e=``){return document.createTextNode(e)}function on(e){return tn.call(e)}function sn(e){return nn.call(e)}function R(e,t){if(!j)return on(e);var n=on(M);if(n===null)n=M.appendChild(an());else if(t&&n.nodeType!==3){var r=an();return n?.before(r),be(r),r}return t&&dn(n),be(n),n}function z(e,t=!1){if(!j){var n=on(e);return n instanceof Comment&&n.data===``?sn(n):n}if(t){if(M?.nodeType!==3){var r=an();return M?.before(r),be(r),r}dn(M)}return M}function B(e,t=!1){if(!j)return on(e);var n=R(e,t);return N(e),n}function V(e,t=1,n=!1){let r=j?M:e;for(var i;t--;)i=r,r=sn(r);if(!j)return r;if(n){if(r?.nodeType!==3){var a=an();return r===null?i?.after(a):r.before(a),be(a),a}dn(r)}return be(r),r}function cn(e){e.textContent=``}function ln(){return!1}function un(e,t,n){return t==null||t===`http://www.w3.org/1999/xhtml`?n?document.createElement(e,{is:n}):document.createElement(e):n?document.createElementNS(t,e,{is:n}):document.createElementNS(t,e)}function dn(e){if(e.nodeValue.length<65536)return;let t=e.nextSibling;for(;t!==null&&t.nodeType===3;)t.remove(),e.nodeValue+=t.nodeValue,t=e.nextSibling}function fn(e){var t=W;if(t===null)return U.f|=ne,e;if(!(t.f&32768)&&!(t.f&4))throw e;pn(e,t)}function pn(e,t){if(!(t!==null&&t.f&16384)){for(;t!==null;){if(t.f&128&&!(t.f&33570816)){if(!(t.f&32768))throw e;try{t.b.error(e);return}catch(t){e=t}}t=t.parent}throw e}}function mn(e){W===null&&(U===null&&Ne(e),Me()),Vn&&je(e)}function hn(e,t){var n=t.last;n===null?t.last=t.first=e:(n.next=e,e.prev=n,t.last=e)}function gn(e,t){var n=W;n!==null&&n.f&8192&&(e|=x);var r={ctx:He,deps:null,nodes:null,f:e|y|512,first:null,fn:t,last:null,next:null,parent:n,b:n&&n.b,prev:null,teardown:null,wv:0,ac:null};F?.register_created_effect(r);var i=r;if(e&4)Et===null?At.ensure().schedule(r):Et.push(r);else if(t!==null){try{lr(r)}catch(e){throw jn(r),e}i.deps===null&&i.teardown===null&&i.nodes===null&&i.first===i.last&&!(i.f&524288)&&(i=i.first,e&16&&e&65536&&i!==null&&(i.f|=T))}if(i!==null&&(i.parent=n,n!==null&&hn(i,n),U!==null&&U.f&2&&!(e&64))){var a=U;(a.effects??=[]).push(i)}return r}function _n(){return U!==null&&!Un}function vn(e){let t=gn(8,null);return $e(t,v),t.teardown=e,t}function yn(e){mn(`$effect`);var t=W.f;if(!U&&t&32&&He!==null&&!He.i){var n=He;(n.e??=[]).push(e)}else return bn(e)}function bn(e){return gn(4|D,e)}function xn(e){return mn(`$effect.pre`),gn(8|D,e)}function Sn(e){At.ensure();let t=gn(64|E,e);return(e={})=>new Promise(n=>{e.outro?Pn(t,()=>{jn(t),n(void 0)}):(jn(t),n(void 0))})}function Cn(e){return gn(4,e)}function wn(e){return gn(O|E,e)}function Tn(e,t=0){return gn(8|t,e)}function H(e,t=[],n=[],r=[]){st(r,t,n,t=>{gn(8,()=>{e(...t.map(G))})})}function En(e,t=0){return gn(16|t,e)}function Dn(e){return gn(32|E,e)}function On(e){var t=e.teardown;if(t!==null){let n=Vn,r=U;Hn(!0),Wn(null);try{t.call(null)}catch(t){pn(t,e.parent)}finally{Hn(n),Wn(r)}}}function kn(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){let e=n.ac;e!==null&&at(()=>{e.abort(de)});var r=n.next;n.f&64?n.parent=null:jn(n,t),n=r}}function An(e){for(var t=e.first;t!==null;){var n=t.next;t.f&32||jn(t),t=n}}function jn(e,t=!0){var n=!1;(t||e.f&262144)&&e.nodes!==null&&e.nodes.end!==null&&(Mn(e.nodes.start,e.nodes.end),n=!0),e.f|=w,kn(e,t&&!n),cr(e,0);var r=e.nodes&&e.nodes.t;if(r!==null)for(let e of r)e.stop();On(e),e.f^=w,e.f|=S;var i=e.parent;i!==null&&i.first!==null&&Nn(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes=e.ac=e.b=null}function Mn(e,t){for(;e!==null;){var n=e===t?null:sn(e);e.remove(),e=n}}function Nn(e){var t=e.parent,n=e.prev,r=e.next;n!==null&&(n.next=r),r!==null&&(r.prev=n),t!==null&&(t.first===e&&(t.first=r),t.last===e&&(t.last=n))}function Pn(e,t,n=!0){var r=[];e.f|=256,Fn(e,r,!0);var i=()=>{n&&jn(e),t&&t()},a=r.length;if(a>0){var o=()=>--a||i();for(var s of r)s.out(o)}else i()}function Fn(e,t,n){if(!(e.f&8192)){e.f^=x;var r=e.nodes&&e.nodes.t;if(r!==null)for(let e of r)(e.is_global||n)&&t.push(e);for(var i=e.first;i!==null;){var a=i.next;if(!(i.f&64)){var o=!!(i.f&65536)||!!(i.f&32)&&!!(e.f&16);Fn(i,t,o?n:!1)}i=a}}}function In(e){e.f&=-257,Ln(e,!0)}function Ln(e,t){if(!(e.f&256)&&e.f&8192){e.f^=x,e.f&1024||($e(e,y),At.ensure().schedule(e));for(var n=e.first;n!==null;){var r=n.next,i=!!(n.f&65536)||!!(n.f&32);Ln(n,i?t:!1),n=r}var a=e.nodes&&e.nodes.t;if(a!==null)for(let e of a)(e.is_global||t)&&e.in()}}function Rn(e,t){if(e.nodes)for(var n=e.nodes.start,r=e.nodes.end;n!==null;){var i=n===r?null:sn(n);t.append(n),n=i}}var zn=null,Bn=!1,Vn=!1;function Hn(e){Vn=e}var U=null,Un=!1;function Wn(e){U=e}var W=null;function Gn(e){W=e}var Kn=null;function qn(e){U!==null&&(U.f&2097152||U.f&2)&&(Kn??=new Set).add(e)}var Jn=null,Yn=0,Xn=null;function Zn(e){Xn=e}var Qn=1,$n=0,er=$n;function tr(e){er=e}function nr(){return++Qn}function rr(e){var t=e.f;if(t&2048)return!0;if(t&4096){for(var n=e.deps,r=n.length,i=0;ie.wv)return!0}t&512&&St===null&&$e(e,v)}return!1}function ir(e,t,n=!0){var r=e.reactions;if(r!==null&&!(Kn!==null&&Kn.has(e)))for(var i=0;i{e.ac.abort(de)}),e.ac=null);try{e.f|=te;var u=e.fn,d=u();e.f|=C;var f=or(e);if(qe()&&Xn!==null&&!Un&&f!==null&&!(e.f&6146))for(var p=0;p0)for(t.length=Yn+Jn.length,r=0;r{s.ac.abort(de),s.ac=null,$e(s,y)}),vt(s),cr(s,0)}}function cr(e,t){var n=e.deps;if(n!==null)for(var r=t;rn?.call(this,e))}return e.startsWith(`pointer`)||e.startsWith(`touch`)||e===`wheel`?(i.__removed=!1,Xe(()=>{i.__removed||t.addEventListener(e,i,r)})):t.addEventListener(e,i,r),i}function Sr(e,t,n,r,i){var a={capture:r,passive:i},o=xr(e,t,n,a);(t===document.body||t===window||t===document||t instanceof HTMLMediaElement)&&vn(()=>{o.__removed=!0,t.removeEventListener(e,o,a)})}function K(e,t,n){(t[vr]??={})[e]=n}function Cr(e){for(var t=0;t{Tr=!1,wr=null}));var s=0,c=wr===e&&e[vr];if(c){var l=a.indexOf(c);if(l!==-1&&(t===document||t===window)){e[vr]=t;return}var u=a.indexOf(t);if(u===-1)return;l<=u&&(s=l)}if(o=a[s]||e.target,o!==t){i(e,`currentTarget`,{configurable:!0,get(){return o||n}});var d=U,f=W;Wn(null),Gn(null);try{for(var p,m=[];o!==null&&o!==t;){try{var h=o[vr]?.[r];h==null||o.disabled&&e.target!==o||h.call(o,e)}catch(e){p?m.push(e):p=e}if(e.cancelBubble)break;s++,o=s{throw e});throw p}}finally{e[vr]=t,delete e.currentTarget,Wn(d),Gn(f)}}}var Dr=globalThis?.window?.trustedTypes&&globalThis.window.trustedTypes.createPolicy(`svelte-trusted-html`,{createHTML:e=>e});function Or(e){return Dr?.createHTML(e)??e}function kr(e){var t=un(`template`);return t.innerHTML=Or(e.replaceAll(``,``)),t.content}function Ar(e,t){var n=W;n.nodes===null&&(n.nodes={start:e,end:t,a:null,t:null})}function q(e,t){var n=!!(t&1),r=!!(t&2),i,a=!e.startsWith(``);return()=>{if(j)return Ar(M,null),M;i===void 0&&(i=kr(a?e:``+e),n||(i=on(i)));var t=r||en?document.importNode(i,!0):i.cloneNode(!0);if(n){var o=on(t),s=t.lastChild;Ar(o,s)}else Ar(t,t);return t}}function jr(e,t,n=`svg`){var r=!e.startsWith(``),i=!!(t&1),a=`<${n}>${r?e:``+e}`,o;return()=>{if(j)return Ar(M,null),M;if(!o){var e=on(kr(a));if(i)for(o=document.createDocumentFragment();on(e);)o.appendChild(on(e));else o=on(e)}var t=o.cloneNode(!0);if(i){var n=on(t),r=t.lastChild;Ar(n,r)}else Ar(t,t);return t}}function Mr(e,t){return jr(e,t,`svg`)}function Nr(e=``){if(!j){var t=an(e+``);return Ar(t,t),t}var n=M;return n.nodeType===3?dn(n):(n.before(n=an()),be(n)),Ar(n,n),n}function Pr(){if(j)return Ar(M,null),M;var e=document.createDocumentFragment(),t=document.createComment(``),n=an();return e.append(t,n),Ar(t,n),e}function J(e,t){if(j){var n=W;n.f&32768&&n.nodes.end!==null||(n.nodes.end=M),xe()}else e!==null&&e.before(t)}function Fr(){if(j&&M&&M.nodeType===8&&M.textContent?.startsWith(`$`)){let e=M.textContent.substring(1);return xe(),e}return(window.__svelte??={}).uid??=1,`c${window.__svelte.uid++}`}function Ir(e){let t=0,n=Vt(0),r;return()=>{_n()&&(G(n),Tn(()=>(t===0&&(r=pr(()=>e(()=>Jt(n)))),t+=1,()=>{Xe(()=>{--t,t===0&&(r?.(),r=void 0,Jt(n))})})))}}var Lr=T|E;function Rr(e,t,n,r){new zr(e,t,n,r)}var zr=class{parent;is_pending=!1;transform_error;#e;#t=j?M:null;#n;#r;#i;#a=null;#o=null;#s=null;#c=null;#l=0;#u=0;#d=!1;#f=new Set;#p=new Set;#m=null;#h=Ir(()=>(this.#m=Vt(this.#l),()=>{this.#m=null}));constructor(e,t,n,r){this.#e=e,this.#n=t,this.#r=e=>{var t=W;t.b=this,t.f|=128,n(e)},this.parent=W.b,this.transform_error=r??this.parent?.transform_error??(e=>e),this.#i=En(()=>{if(j){let e=this.#t;xe();let t=e.data===`[!`;if(e.data.startsWith(`[?`)){let t=JSON.parse(e.data.slice(2));this.#_(t)}else t?this.#y():this.#g()}else this.#b()},Lr),j&&(this.#e=M)}#g(){try{this.#a=Dn(()=>this.#r(this.#e))}catch(e){this.error(e)}}#_(e){let t=this.#n.failed,{reset:n,invoke_onerror:r}=this.#v(e);Xe(r),t&&(this.#s=Dn(()=>{t(this.#e,()=>e,()=>n)}))}#v(e){var t=!1,n=!1;let r=()=>{t?ve():(t=!0,n&&ze(),this.#s!==null&&Pn(this.#s,()=>{this.#s=null}),this.#S(()=>{this.#b()}))};return{reset:r,invoke_onerror:()=>{try{n=!0,this.#n.onerror?.(e,r),n=!1}catch(e){pn(e,this.#i&&this.#i.parent)}}}}#y(){let e=this.#n.pending;e&&(this.is_pending=!0,this.#o=Dn(()=>e(this.#e)),Xe(()=>{var e=this.#c=document.createDocumentFragment(),t=an(),n=!1;e.append(t),this.#a=this.#S(()=>{try{return Dn(()=>this.#r(t))}catch(e){try{this.error(e),n=!0}catch(e){pn(e,this.#i.parent)}return null}}),this.#a===null?(this.#c=null,n&&this.#x(F)):this.#u===0&&(this.#e.before(e),this.#c=null,Pn(this.#o,()=>{this.#o=null}),this.#x(F))}))}#b(){try{if(this.is_pending=this.has_pending_snippet(),this.#u=0,this.#l=0,this.#a=Dn(()=>{this.#r(this.#e)}),this.#u>0){var e=this.#c=document.createDocumentFragment();Rn(this.#a,e);let t=this.#n.pending;this.#o=Dn(()=>t(this.#e))}else this.#x(F)}catch(e){this.error(e)}}#x(e){this.is_pending=!1,e.transfer_effects(this.#f,this.#p)}defer_effect(e){tt(e,this.#f,this.#p)}is_rendered(){return!this.is_pending&&(!this.parent||this.parent.is_rendered())}has_pending_snippet(){return!!this.#n.pending}#S(e){var t=W,n=U,r=He;Gn(this.#i),Wn(this.#i),Ue(this.#i.ctx);try{return At.ensure(),e()}finally{Gn(t),Wn(n),Ue(r)}}#C(e,t){this.has_pending_snippet()?(this.#u+=e,this.#u===0&&(this.#x(t),this.#o&&Pn(this.#o,()=>{this.#o=null}),this.#c&&=(this.#e.before(this.#c),null))):this.parent&&this.parent.#C(e,t)}update_pending_count(e,t){this.#C(e,t),this.#l+=e,!(!this.#m||this.#d)&&(this.#d=!0,Xe(()=>{this.#d=!1,this.#m&&Gt(this.#m,this.#l)}))}get_effect_pending(){return this.#h(),G(this.#m)}error(e){if(!this.#n.onerror&&!this.#n.failed)throw e;F?.is_fork?(this.#a&&F.skip_effect(this.#a),this.#o&&F.skip_effect(this.#o),this.#s&&F.skip_effect(this.#s),F.oncommit(()=>{this.#w(e)})):this.#w(e)}#w(e){this.#a&&=(jn(this.#a),null),this.#o&&=(jn(this.#o),null),this.#s&&=(jn(this.#s),null),j&&(be(this.#t),Se(),be(Ce()));let t=this.#n.failed,n=e=>{let{reset:n,invoke_onerror:r}=this.#v(e);r(),t&&(this.#s=this.#S(()=>{try{return Dn(()=>{var r=W;r.b=this,r.f|=128,t(this.#e,()=>e,()=>n)})}catch(e){return pn(e,this.#i.parent),null}}))};Xe(()=>{var t;try{t=this.transform_error(e)}catch(e){pn(e,this.#i&&this.#i.parent);return}typeof t==`object`&&t&&typeof t.then==`function`?t.then(n,e=>pn(e,this.#i&&this.#i.parent)):n(t)})}};function Y(e,t){var n=t==null?``:typeof t==`object`?`${t}`:t;n!==(e[le]??=e.nodeValue)&&(e[le]=n,e.nodeValue=`${n}`)}function Br(e,t){return Hr(e,t)}var Vr=new Map;function Hr(e,{target:t,anchor:n,props:i={},events:a,context:o,intro:s=!0,transformError:c}){rn();var l=void 0,u=Sn(()=>{var s=n??t.appendChild(an());Rr(s,{pending:()=>{}},t=>{We({});var n=He;if(o&&(n.c=o),a&&(i.$$events=a),j&&Ar(t,null),l=e(t,i)||Ke(),j&&(W.nodes.end=M,M===null||M.nodeType!==8||M.data!==`]`))throw ge(),pe;Ge()},c);var u=new Set,d=e=>{for(var n=0;n{for(var e of u)for(let n of[t,document]){var r=Vr.get(n),i=r.get(e);--i==0?(n.removeEventListener(e,Er),r.delete(e),r.size===0&&Vr.delete(n)):r.set(e,i)}br.delete(d),s!==n&&s.parentNode?.removeChild(s)}});return Ur.set(l,u),l}var Ur=new WeakMap,Wr=class{anchor;#e=new Map;#t=new Map;#n=new Map;#r=new Set;#i=!0;constructor(e,t=!0){this.anchor=e,this.#i=t}#a=e=>{if(this.#e.has(e)){var t=this.#e.get(e),n=this.#t.get(t);if(n)In(n),this.#r.delete(t);else{var r=this.#n.get(t);r&&(In(r.effect),this.#t.set(t,r.effect),this.#n.delete(t),r.fragment.lastChild.remove(),this.anchor.before(r.fragment),n=r.effect)}for(let[t,n]of this.#e){if(this.#e.delete(t),t===e)break;let r=this.#n.get(n);r&&(jn(r.effect),this.#n.delete(n))}for(let[e,r]of this.#t){if(e===t||this.#r.has(e))continue;let i=()=>{if(Array.from(this.#e.values()).includes(e)){var t=document.createDocumentFragment();Rn(r,t),t.append(an()),this.#n.set(e,{effect:r,fragment:t})}else jn(r);this.#r.delete(e),this.#t.delete(e)};this.#i||!n?(this.#r.add(e),Pn(r,i,!1)):i()}}};#o=e=>{this.#e.delete(e);let t=Array.from(this.#e.values());for(let[e,n]of this.#n)t.includes(e)||(jn(n.effect),this.#n.delete(e))};ensure(e,t){var n=F,r=ln();if(t&&!this.#t.has(e)&&!this.#n.has(e)){if(r){var i=document.createDocumentFragment(),a=an();i.append(a),this.#n.set(e,{effect:Dn(()=>t(a)),fragment:i})}else this.#t.set(e,Dn(()=>t(this.anchor)))}if(this.#e.set(n,e),r){for(let[t,r]of this.#t)t===e?n.unskip_effect(r):n.skip_effect(r);for(let[t,r]of this.#n)t===e?n.unskip_effect(r.effect):n.skip_effect(r.effect);n.oncommit(this.#a),n.ondiscard(this.#o)}else j&&(this.anchor=M),this.#a(n)}};function X(e,t,n=!1){var r;j&&(r=M,xe());var i=new Wr(e),a=n?T:0;function o(e,t){if(j){var n=we(r);if(e!==parseInt(n.substring(1))){var a=Ce();be(a),i.anchor=a,ye(!1),i.ensure(e,t),ye(!0);return}}i.ensure(e,t)}En(()=>{var e=!1;t((t,n=0)=>{e=!0,o(n,t)}),e||o(-1,null)},a)}var Gr=Symbol(`NaN`);function Kr(e,t,n){j&&xe();var r=new Wr(e),i=!qe();En(()=>{var e=t();e!==e&&(e=Gr),i&&typeof e==`object`&&e&&(e={}),r.ensure(e,n)})}function qr(e,t){return t}function Jr(e,t,n){for(var i=[],a=t.length,o,s=t.length,c=0;c{if(o){if(o.pending.delete(n),o.done.add(n),o.pending.size===0){var t=e.outrogroups;Yr(e,r(o.done)),t.delete(o),t.size===0&&(e.outrogroups=null)}}else--s},!1)}if(s===0){var l=i.length===0&&n!==null&&e.pending.size===0;if(l){var u=n,d=u.parentNode;cn(d),d.append(u),e.items.clear()}Yr(e,t,!l)}else o={pending:new Set(t),done:new Set},(e.outrogroups??=new Set).add(o)}function Yr(e,t,n=!0){var r;if(e.pending.size>0){r=new Set;for(let t of e.pending.values())for(let n of t)r.add(e.items.get(n).e)}for(var i=0;i{var t=i();return e(t)?t:t==null?[]:r(t)}),p,m=new Map,h=!0;function g(e){v.effect.f&16384||(v.pending.delete(e),v.fallback=d,Qr(v,p,c,n,a),d!==null&&(p.length===0?d.f&33554432?(d.f^=ee,ei(d,null,c)):In(d):Pn(d,()=>{d=null})))}function _(e){v.pending.delete(e)}var v={effect:En(()=>{p=G(f);var e=p.length;let t=!1;j&&we(c)===`[!`!=(e===0)&&(c=Ce(),be(c),ye(!1),t=!0);for(var r=new Set,u=F,v=ln(),y=0;ys(c)):(d=Dn(()=>s(Xr??=an())),d.f|=ee)),e>r.size&&Ae(``,``,``),j&&e>0&&be(Ce()),!h){if(m.set(u,r),v){for(let[e,t]of l)r.has(e)||u.skip_effect(t.e);u.oncommit(g),u.ondiscard(_)}else g(u)}t&&ye(!0),G(f)}),flags:n,items:l,pending:m,outrogroups:null,fallback:d};h=!1,j&&(c=M)}function Zr(e){for(;e!==null&&!(e.f&32);)e=e.next;return e}function Qr(e,t,n,i,a){var o=!!(i&8),s=t.length,c=e.items,l=Zr(e.effect.first),u,d=null,f,p=[],m=[],h,g,_,v;if(o)for(v=0;v0){var E=i&4&&s===0?n:null;if(o){for(v=0;v{if(f!==void 0)for(_ of f)_.nodes?.a?.apply()})}function $r(e,t,n,r,i,a,o,s){var c=o&1?o&16?Vt(n):Ht(n,!1,!1):null,l=o&2?Vt(i):null;return{v:c,i:l,e:Dn(()=>(a(t,c??n,l??i,s),()=>{e.delete(r)}))}}function ei(e,t,n){if(e.nodes)for(var r=e.nodes.start,i=e.nodes.end,a=t&&!(t.f&33554432)?t.nodes.start:n;r!==null;){var o=sn(r);if(a.before(r),r===i)return;r=o}}function ti(e,t,n){t===null?e.effect.first=n:t.next=n,n===null?e.effect.last=t:n.prev=t}function ni(e,t,...n){var r=new Wr(e);En(()=>{let e=t()??null;r.ensure(e,e&&(t=>e(t,...n)))},T)}function ri(e,t,n){var r;j&&(r=M,xe());var i=new Wr(e);En(()=>{var e=t()??null;if(j&&we(r)===`[`!=(e!==null)){var a=Ce();be(a),i.anchor=a,ye(!1),i.ensure(e,e&&(t=>n(t,e))),ye(!0)}else i.ensure(e,e&&(t=>n(t,e)))},T)}var ii=[...` -\r\f\xA0\v`];function ai(e,t,n){var r=e==null?``:``+e;if(t&&(r=r?r+` `+t:t),n){for(var i of Object.keys(n))if(n[i])r=r?r+` `+i:i;else if(r.length)for(var a=i.length,o=0;(o=r.indexOf(i,o))>=0;){var s=o+a;(o===0||ii.includes(r[o-1]))&&(s===r.length||ii.includes(r[s]))?r=(o===0?``:r.substring(0,o))+r.substring(s+1):o=s}}return r===``?null:r}function oi(e,t=!1){var n=t?` !important;`:`;`,r=``;for(var i of Object.keys(e)){var a=e[i];a!=null&&a!==``&&(r+=` `+i+`: `+a+n)}return r}function si(e){return e[0]!==`-`||e[1]!==`-`?e.toLowerCase():e}function ci(e,t){if(t){var n=``,r,i;if(Array.isArray(t)?(r=t[0],i=t[1]):r=t,e){e=String(e).replaceAll(/\/\*.*?\*\//g,``).trim();var a=!1,o=0,s=!1,c=[];r&&c.push(...Object.keys(r).map(si)),i&&c.push(...Object.keys(i).map(si));var l=0,u=-1;let t=e.length;for(var d=0;d{t.every(vi)||(`__defaultValue`in e&&pi(e,!1),`__value`in e&&mi(e,e.__value))});t.observe(e,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[`value`]}),vn(()=>{t.disconnect()})}function gi(e,t,n=t){var r=new WeakSet,i=!0;ot(e,`change`,t=>{var i=t?`[selected]`:`:checked`,a;if(e.multiple)a=[].map.call(e.querySelectorAll(i),_i);else{var o=e.querySelector(i)??e.querySelector(`option:not([disabled])`);a=o&&_i(o)}n(a),e.__value=a,F!==null&&r.add(F)}),Cn(()=>{var a=t();if(e===document.activeElement){var o=F;if(r.has(o))return}if(mi(e,a,i),i&&a===void 0){var s=e.querySelector(`:checked`);s!==null&&(a=_i(s),n(a))}e.__value=a,i=!1})}function _i(e){return`__value`in e?e.__value:e.value}function vi(e){if(e.target.closest(`selectedcontent`)!==null)return!0;if(e.type===`childList`){var t=[...e.addedNodes,...e.removedNodes];return t.length>0&&t.every(e=>e.nodeName===`SELECTEDCONTENT`)}return!1}var yi=Symbol(`is custom element`),bi=Symbol(`is html`),xi=fe?`link`:`LINK`;function Si(e){if(j){var t=!1,n=()=>{if(!t){if(t=!0,e.hasAttribute(`value`)){var n=e.value;Q(e,`value`,null),e.value=n}if(e.hasAttribute(`checked`)){var r=e.checked;Q(e,`checked`,null),e.checked=r}}};e[ue]=n,Xe(n),it()}}function Ci(e,t){var n=wi(e);n.checked!==(n.checked=t??void 0)&&(e.checked=t)}function Q(e,t,n,r){var i=wi(e);j&&(i[t]=e.getAttribute(t),t===`src`||t===`srcset`||t===`href`&&e.nodeName===xi)||i[t]!==(i[t]=n)&&(t===`loading`&&(e[ae]=n),n==null?e.removeAttribute(t):typeof n!=`string`&&Ei(e).has(t)?e[t]=n:e.setAttribute(t,n))}function wi(e){return e[oe]??={[yi]:e.nodeName.includes(`-`),[bi]:e.namespaceURI===me}}var Ti=new Map;function Ei(e){var t=e.getAttribute(`is`)||e.nodeName,n=Ti.get(t);if(n)return n;Ti.set(t,n=new Set);for(var r,i=e,a=Element.prototype;a!==i;){for(var s in r=o(i),r)r[s].set&&s!==`innerHTML`&&s!==`textContent`&&s!==`innerText`&&n.add(s);i=l(i)}return n}function Di(e,t,n=t){var r=new WeakSet;ot(e,`input`,async i=>{var a=i?e.defaultValue:e.value;if(a=Mi(e)?Ni(a):a,n(a),F!==null&&r.add(F),await ur(),a!==(a=t())){var o=e.selectionStart,s=e.selectionEnd,c=e.value.length;if(e.value=a??``,s!==null){var l=e.value.length;o===s&&s===c&&l>c?(e.selectionStart=l,e.selectionEnd=l):(e.selectionStart=o,e.selectionEnd=Math.min(s,l))}}}),(j&&e.defaultValue!==e.value||pr(t)==null&&e.value)&&(n(Mi(e)?Ni(e.value):e.value),F!==null&&r.add(F)),Tn(()=>{var n=t();if(e===document.activeElement){var i=F;if(r.has(i))return}Mi(e)&&n===Ni(e.value)||(e.type!==`date`||n||e.value)&&n!==e.value&&(e.value=n??``)})}var Oi=new Set;function ki(e,t,n,r,i=r){var a=n.getAttribute(`type`)===`checkbox`,o=e;let s=!1;if(t!==null)for(var c of t)o=o[c]??=[];o.push(n),ot(n,`change`,()=>{var e=n.__value;a&&(e=ji(o,e,n.checked)),i(e)},()=>i(a?[]:null)),Tn(()=>{var e=r();j&&n.defaultChecked!==n.checked?s=!0:a?(e||=[],n.checked=e.includes(n.__value)):n.checked=Qt(n.__value,e)}),vn(()=>{var e=o.indexOf(n);e!==-1&&o.splice(e,1)}),Oi.has(o)||(Oi.add(o),Xe(()=>{o.sort((e,t)=>e.compareDocumentPosition(t)===4?-1:1),Oi.delete(o)})),Xe(()=>{if(s){var e=a?ji(o,e,n.checked):o.find(e=>e.checked)?.__value;i(e)}})}function Ai(e,t,n=t){ot(e,`change`,t=>{n(t?e.defaultChecked:e.checked)}),(j&&e.defaultChecked!==e.checked||pr(t)==null)&&n(e.checked),Tn(()=>{e.checked=!!t()})}function ji(e,t,n){for(var r=new Set,i=0;i{var n=this.#e.get(e);n.delete(t),n.size===0&&(this.#e.delete(e),this.#t.unobserve(e))}}#r(){return this.#t??=new ResizeObserver(t=>{for(var n of t){e.entries.set(n.target,n);for(var r of this.#e.get(n.target)||[])r(n)}})}}({box:`border-box`});function Fi(e,t,n){var r=Pi.observe(e,()=>n(e[t]));Cn(()=>(pr(()=>n(e[t])),r))}function Ii(e,t){return e===t||e?.[k]===t}function Li(e=Ke(),t,n,r){var i=He.r,a=W;return Cn(()=>{var o,s;return Tn(()=>{o=s,s=r?.()||[],pr(()=>{Ii(n(...s),e)||(t(e,...s),o&&Ii(n(...o),e)&&t(null,...o))})}),()=>{let r=a;for(;r!==i&&r.parent!==null&&r.parent.f&33554432;)r=r.parent;let o=()=>{s&&Ii(n(...s),e)&&t(null,...s)},c=r.teardown;r.teardown=()=>{o(),c?.()}}}),e}function Ri(e,t,n,r,i){var a=()=>{r(n[e])};n.addEventListener(t,a),i?Tn(()=>{n[e]=i()}):a(),(n===document.body||n===window||n===document)&&vn(()=>{n.removeEventListener(t,a)})}function zi(e=!1){let t=He,n=t.l.u;if(!n)return;let r=()=>mr(t.s);if(e){let e=0,n={},i=dt(()=>{let r=!1,i=t.s;for(let e in i)i[e]!==n[e]&&(n[e]=i[e],r=!0);return r&&e++,e});r=()=>G(i)}n.b.length&&xn(()=>{Bi(t,r),m(n.b)}),yn(()=>{let e=pr(()=>n.m.map(p));return()=>{for(let t of e)typeof t==`function`&&t()}}),n.a.length&&yn(()=>{Bi(t,r),m(n.a)})}function Bi(e,t){if(e.l.s)for(let t of e.l.s)G(t);t()}var Vi=!1;function Hi(e){var t=Vi;try{return Vi=!1,[e(),Vi]}finally{Vi=t}}var Ui={get(e,t){let n=e.props.length;for(;n--;){let r=e.props[n];if(d(r)&&(r=r()),typeof r==`object`&&r&&t in r)return r[t]}},set(e,t,n){let r=e.props.length;for(;r--;){let i=e.props[r];d(i)&&(i=i());let o=a(i,t);if(o&&o.set)return o.set(n),!0}return!1},getOwnPropertyDescriptor(e,t){let n=e.props.length;for(;n--;){let r=e.props[n];if(d(r)&&(r=r()),typeof r==`object`&&r&&t in r){let e=a(r,t);return e&&!e.configurable&&(e.configurable=!0),e}}},has(e,t){if(t===k||t===ie)return!1;for(let n of e.props)if(d(n)&&(n=n()),n!=null&&t in n)return!0;return!1},ownKeys(e){let t=[];for(let n of e.props)if(d(n)&&(n=n()),n){for(let e in n)t.includes(e)||t.push(e);for(let e of Object.getOwnPropertySymbols(n))t.includes(e)||t.push(e)}return t}};function Wi(...e){return new Proxy({props:e},Ui)}function Gi(e,t,n,r){var i=!Be||!!(n&2),o=!!(n&8),s=!!(n&16),c=r,l=!0,u=void 0,d=()=>s&&i?(u??=dt(r),G(u)):(l&&(l=!1,c=s?pr(r):r),c);let f;if(o){var p=k in e||ie in e;f=a(e,t)?.set??(p&&t in e?n=>e[t]=n:void 0)}var m,h=!1;o?[m,h]=Hi(()=>e[t]):m=e[t],m===void 0&&r!==void 0&&(m=d(),f&&(i&&Fe(t),f(m)));var g=i?()=>{var n=e[t];return n===void 0?d():(l=!0,n)}:()=>{var n=e[t];return n!==void 0&&(c=void 0),n===void 0?c:n};if(i&&!(n&4))return g;if(f){var _=e.$$legacy;return(function(e,t){return arguments.length>0?((!i||!t||_||h)&&f(t?g():e),e):g()})}var v=!1,y=(n&1?dt:mt)(()=>(v=!1,g()));o&&G(y);var b=W;return(function(e,t){if(arguments.length>0){let n=t?G(y):i&&o?Xt(e):e;return L(y,n),v=!0,c!==void 0&&(c=n),e}return Vn&&v||b.f&16384?y.v:G(y)})}function Ki(e){He===null&&Oe(`onMount`),Be&&He.l!==null?Ji(He).m.push(e):yn(()=>{let t=pr(e);if(typeof t==`function`)return t})}function qi(e){He===null&&Oe(`onDestroy`),Ki(()=>()=>pr(e))}function Ji(e){var t=e.l;return t.u??={a:[],b:[],m:[]}}typeof window<`u`&&((window.__svelte??={}).v??=new Set).add(`5`);function Yi(e,t){if(e instanceof RegExp)return{keys:!1,pattern:e};var n,r,i,a,o=[],s=``,c=e.split(`/`);for(c[0]||c.shift();i=c.shift();)n=i[0],n===`*`?(o.push(`wild`),s+=`/(.*)`):n===`:`?(r=i.indexOf(`?`,1),a=i.indexOf(`.`,1),o.push(i.substring(1,~r?r:~a?a:i.length)),s+=~r&&!~a?`(?:/([^/]+?))?`:`/([^/]+?)`,~a&&(s+=(~r?`?`:``)+`\\`+i.substring(a))):s+=`/`+i;return{keys:o,pattern:RegExp(`^`+s+(t?`(?=$|/)`:`/?$`),`i`)}}var Xi=new class{#e=I(Zi());get _loc(){return G(this.#e)}set _loc(e){L(this.#e,e)}#t=P(()=>this._loc.location);get _location(){return G(this.#t)}set _location(e){L(this.#t,e)}#n=P(()=>this._loc.querystring);get _querystring(){return G(this.#n)}set _querystring(e){L(this.#n,e)}#r=I(void 0);get _params(){return G(this.#r)}set _params(e){L(this.#r,e)}get loc(){return this._loc}get location(){return this._location}get querystring(){return this._querystring}get params(){return this._params}constructor(){typeof window<`u`?window.addEventListener(`hashchange`,()=>{this._loc=Zi()}):console.warn(`[svelte-spa-router] window 'window' is not defined, skipping initiation`)}};function Zi(){let e=typeof window<`u`?window.location.href:``,t=e.indexOf(`#/`),n=t>-1?e.substr(t+1):`/`,r=n.indexOf(`?`),i=``;return r>-1&&(i=n.substr(r+1),n=n.substr(0,r)),{location:n,querystring:i}}async function Qi(e){if(!e||e.length<1||e.charAt(0)!=`/`&&e.indexOf(`#/`)!==0)throw Error(`Invalid parameter location`);await ur();let t=(e.charAt(0)==`#`?``:`#`)+e;try{let e={...history.state};delete e.__svelte_spa_router_scrollX,delete e.__svelte_spa_router_scrollY,window.history.replaceState(e,``,t)}catch{console.warn("Caught exception while replacing the current page. If you're running this in the Svelte REPL, please note that the `replace` method might not work in this environment.")}window.dispatchEvent(new Event(`hashchange`))}function $i(e){e?window.scrollTo(e.__svelte_spa_router_scrollX||0,e.__svelte_spa_router_scrollY||0):window.scrollTo(0,0)}function ea(e,t){We(t,!0);let n=Gi(t,`routes`,19,()=>({})),r=Gi(t,`prefix`,3,``),i=Gi(t,`restoreScrollState`,3,!1),a=Gi(t,`onConditionsFailed`,3,()=>{}),o=Gi(t,`onRouteLoaded`,3,()=>{}),s=Gi(t,`onRouteLoading`,3,()=>{}),c=Gi(t,`onRouteEvent`,3,()=>{});class l{path;component;conditions;userData;props;_pattern;_keys;constructor(e,t){let n=e=>typeof e==`object`&&!!e&&e._sveltesparouter===!0;if(!t||typeof t!=`function`&&!n(t))throw Error(`Invalid component object`);if(!e||typeof e==`string`&&(e.length<1||e.charAt(0)!=`/`&&e.charAt(0)!=`*`)||typeof e==`object`&&!(e instanceof RegExp))throw Error(`Invalid value for "path" argument - strings must start with / or *`);let r=Yi(e);if(this.path=e,n(t)){let e=t;this.component=e.component,this.conditions=e.conditions||[],this.userData=e.userData,this.props=e.props||{}}else{let e=t;this.component=()=>Promise.resolve(e),this.conditions=[],this.props={}}this._pattern=r.pattern,this._keys=r.keys}match(e){if(r()){if(typeof r()==`string`){if(e.startsWith(r()))e=e.substr(r().length)||`/`;else return null}else if(r()instanceof RegExp){let t=e.match(r());if(t&&t[0])e=e.substr(t[0].length)||`/`;else return null}}let t=this._pattern.exec(e);if(t===null)return null;if(this._keys===!1)return t;let n={},i=0;for(;i{u.push(new l(t,e))}):Object.keys(n()).forEach(e=>{let t=n();u.push(new l(e,t[e]))});let d=I(null),f=I(null),p=I({}),m=null,h=null;yn(()=>{history.scrollRestoration=i()?`manual`:`auto`}),yn(()=>{if(!i())return;let e=e=>{m=e.state&&(e.state.__svelte_spa_router_scrollY||e.state.__svelte_spa_router_scrollX)?e.state:null};return window.addEventListener(`popstate`,e),()=>window.removeEventListener(`popstate`,e)});async function g(e,t){await ur(),e(t)}yn(()=>{let e=Xi.loc,t=!1;return pr(async()=>{let n=0;for(;n{t=!0}});function _(e){return e&&typeof e==`object`&&Object.keys(e).length?e:null}var v=Pr(),y=z(v),b=e=>{let t=P(()=>G(d));var n=Pr(),r=z(n),i=e=>{var n=Pr();ri(z(n),()=>G(t),(e,t)=>{t(e,Wi({get params(){return G(f)},get onRouteEvent(){return c()}},()=>G(p)))}),J(e,n)},a=e=>{var n=Pr();ri(z(n),()=>G(t),(e,t)=>{t(e,Wi({get onRouteEvent(){return c()}},()=>G(p)))}),J(e,n)};X(r,e=>{G(f)?e(i):e(a,-1)}),J(e,n)};X(y,e=>{G(d)&&e(b)}),J(e,v),Ge()}var ta=[`bars`,`pace`,`pie`,`fuel`,`resets`,`thresholds`];function na(e){return e===`zone`||e===`consumption-pace`?`pace`:typeof e==`string`&&ta.includes(e)?e:`bars`}var ra=`codexometer.web.preferences.v1`,ia={statusLine:null,tab:`quota`,view:`bars`,selected:``,defaultDetail:0,layouts:[]};function aa(){try{let e=JSON.parse(localStorage.getItem(ra)||`null`);return!e||typeof e!=`object`?ia:{statusLine:Array.isArray(e.statusLine)?[...new Set(e.statusLine.filter(e=>typeof e==`string`&&e.length<64))].slice(0,32):null,tab:[`quota`,`sessions`,`usage`].includes(e.tab)?e.tab:`quota`,view:na(e.view),selected:typeof e.selected==`string`?e.selected.slice(0,256):``,defaultDetail:+(e.defaultDetail===1),layouts:Array.isArray(e.layouts)?e.layouts.filter(e=>{if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.id==`string`&&t.id.length<=256&&[0,1,2].includes(t.level)}).slice(-100):[]}}catch{return ia}}var oa=Xt(aa()),sa=Xt({open:!1}),ca=Xt({requests:0});function la(){let e=JSON.stringify(oa);try{localStorage.setItem(ra,e)}catch{}}function ua(){return oa.tab===`quota`?`/quota/`+oa.view:`/`+oa.tab}function da(e){return oa.layouts.find(t=>t.id===e)?.level??oa.defaultDetail}function fa(e){oa.defaultDetail=e,oa.layouts=[]}function pa(e,t){oa.layouts=[...oa.layouts.filter(t=>t.id!==e),{id:e,level:Math.max(0,Math.min(2,t))}].slice(-100)}var $=Xt({data:null,connected:!1,error:``}),ma=e=>e==null?`Unavailable`:e.toLocaleString(`en-GB`),ha=e=>!e||String(e).startsWith(`0001-`)?`Not yet available`:new Date(typeof e==`number`?e*1e3:e).toLocaleString(`en-GB`),ga=`codexometer.web.session`,_a=class extends Error{},va;async function ya(e,t,n){if(!va||!$.connected||!$.data?.control||$.data.sessionsError)throw Error(`Session controls unavailable. Check the live connection.`);return await va(e,t,n)}function ba(){let e=new AbortController,t,n=``;va=async(t,r,i)=>{let a=await fetch(`/api/control/`+t,{method:`POST`,headers:{Authorization:`Bearer ${n}`,"Content-Type":`application/json`},body:JSON.stringify(r),signal:AbortSignal.any([e.signal,AbortSignal.timeout(8e3),...i?[i]:[]]),cache:`no-store`});if(!a.ok)throw a.status===502?Error(`Outcome uncertain. Check Codex before taking another action; nothing was retried.`):new _a(`Action rejected, expired or changed. Refresh and check Codex; nothing was retried.`);return a.json()};try{n=sessionStorage.getItem(ga)||``}catch{}let r=location.hash,i=r.startsWith(`#pair=`)?r.slice(6):``;(i||!r||r===`#`)&&(history.replaceState(null,``,`/#`+ua()),window.dispatchEvent(new HashChangeEvent(`hashchange`)));async function a(){if(i)try{let t=await fetch(`/api/pair`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({secret:i}),signal:e.signal});if(!t.ok)throw Error(`Pairing link expired or already used. Restart --web for a fresh link.`);n=(await t.json()).token;try{sessionStorage.setItem(ga,n)}catch{}}catch(t){e.signal.aborted||($.error=t instanceof Error?t.message:`Pairing failed`);return}n?await o():$.error=`Open the private pairing link printed by codexometer --web in your terminal.`}async function o(){try{let t=await fetch(`/api/events`,{headers:{Authorization:`Bearer ${n}`},signal:e.signal,cache:`no-store`});if(t.status===401){try{sessionStorage.removeItem(ga)}catch{}$.error=`Browser session expired. Restart --web and open its new pairing link.`;return}if(!t.ok||!t.body)throw Error(`Live connection unavailable`);let r=t.body.getReader(),i=new TextDecoder,a=``;for(;!e.signal.aborted;){let{done:e,value:t}=await r.read();if(e)break;a+=i.decode(t,{stream:!0});let n;for(;(n=a.indexOf(` - -`))>=0;){let e=a.slice(0,n);a=a.slice(n+2),e.startsWith(`data: `)&&($.data=JSON.parse(e.slice(6)),$.connected=!0,$.error=``)}}}catch{}finally{$.connected=!1}e.signal.aborted||($.error=`Connection lost — showing last observation. Reconnecting…`,t=setTimeout(o,2500))}return a(),()=>{va=void 0,e.abort(),clearTimeout(t),$.connected=!1}}Ve();var xa=q(`

      Quota refresh failed. Policy state is based on the last successful - observation.

      `),Sa=q(`

      `),Ca=q(`

      The longest Codex quota window selects one active model profile. ASK - creates a per-session review; AUTO applies the profile on the next - eligible check.

      `,1),wa=q(`

      No threshold-based model steps were configured at launch.

      `);function Ta(e,t){We(t,!1),zi();var n=Pr(),r=z(n),i=e=>{var t=Ca(),n=z(t),r=B(n),i=V(n,2),a=e=>{J(e,xa())};X(i,e=>{$.data.quotaError&&e(a)});var o=V(i,2),s=R(o),c=B(s),l=V(s,4);Z(l,5,()=>$.data.thresholds,qr,(e,t)=>{var n=Sa();let r;var i=R(n),a=B(i),o=V(i,2),s=R(o),c=B(s,!0),l=B(V(s,2));N(o);var u=V(o,2),d=B(u,!0),f=B(V(u,2));N(n),H((e,i)=>{r=li(n,1,``,null,r,{active:G(t).state===`ACTIVE`,next:G(t).state===`NEXT`}),Y(a,`${G(t).threshold??``}%`),Y(c,G(t).model),Y(l,`${e??``} REASONING // ${i??``} - SPEED`),Y(d,G(t).mode),Y(f,`${G(t).state??``}${G(t).state===`NEXT`?` // ${G(t).remaining||0} PP TO GO`:``}`)},[()=>G(t).effort.toUpperCase(),()=>G(t).speed.toUpperCase()]),J(e,n)}),N(l),N(o),H(e=>{Y(r,`MODEL STEP POLICY // OBSERVED ${e??``}`),Y(c,`THRESHOLDS // ${$.data.thresholds.length??``} CONFIGURED`)},[()=>ha($.data.quotaAt)]),J(e,t)},a=e=>{J(e,wa())};X(r,e=>{$.data?.thresholds?.length?e(i):e(a,-1)}),J(e,n),Ge()}var Ea=Mr(` `,1),Da=Mr(` `,1),Oa=Mr(` `,1),ka=Mr(` `,1),Aa=q(`
      `,1),ja=q(` `),Ma=q(`
      Quota observations
      Observed atPeriod elapsedConsumedTrail segment
      `),Na=q(`
      OBSERVATION DATA
      `),Pa=q(`
      CONSUMPTIONQUOTA PERIOD ELAPSED
      `,1);function Fa(e,t){let n=Fr();We(t,!0);let r=Gi(t,`trail`,19,()=>[]),i=I(!1),a=I(!0),o=I(`window`),s=[0,25,50,75,100],c={off:0,halfHour:30,hour:60,day:1440,window:0},l=I(400),u=I(240),d=P(()=>G(l)-24),f=P(()=>G(u)-48),p=P(()=>Math.max(1,G(d)-48)),m=P(()=>Math.max(1,G(f)-20)),h=P(()=>48+Math.max(0,Math.min(100,t.elapsed))/100*G(p));function g(e){return G(f)-(e-0)/100*G(m)}let _=P(()=>g(t.used)),v=P(()=>t.used-t.elapsed),y=P(()=>r().map((e,t)=>`${t===0||e.break?`M`:`L`}${48+e.elapsed/100*G(p)} ${g(e.used)}`).join(` `)),b=P(()=>{let e=0;for(let t=r().length-1;t>0;t--)if(r()[t].break){e=t;break}return r().slice(e)}),x=P(()=>G(b).length<2?0:Math.max(0,Date.parse(G(b).at(-1).at)-Date.parse(G(b)[0].at)));function S(e){if(G(x)Date.parse(e.at)>=t).length>=2}let C=P(()=>({off:!0,halfHour:S(18e5),hour:S(36e5),day:S(864e5),window:Number.isFinite(t.elapsed)&&t.elapsed>0&&Number.isFinite(t.used)}));yn(()=>{G(o)!==`window`&&!G(C)[G(o)]&&L(o,`window`)});function w(e){if(e===`off`||!G(C)[e])return[];let t=c[e]*6e4,n=Date.parse(G(b).at(-1).at)-t,r=G(b).findIndex(e=>Date.parse(e.at)>=n);return r<0?[]:G(b).slice(r)}function T(e){if(e.length<2)return null;let t=e.reduce((e,t)=>e+t.elapsed,0)/e.length,n=e.reduce((e,t)=>e+t.used,0)/e.length,r=0,i=0;for(let a of e)r+=(a.elapsed-t)*(a.used-n),i+=(a.elapsed-t)**2;return i>0?Math.max(0,r/i):null}let E=P(()=>{let e=G(o)===`window`&&G(C).window?t.used/t.elapsed:T(w(G(o)));if(e===null||!t.duration)return null;let n=t.used+e*(100-t.elapsed),r=G(o)===`window`?0:t.used-e*t.elapsed,i=e,a=Math.max(0,t.elapsed-c[G(o)]/t.duration*100);G(o)===`window`&&(a=0);let s=100;if(i!==0){let e=(0-r)/i,t=(100-r)/i;a=Math.max(a,Math.min(e,t)),s=Math.min(100,Math.max(e,t))}if(a>s)return null;let l=r+i*a,u=r+i*s,d=Math.hypot((s-a)/100*G(p),(u-l)/100*G(m)),f=Math.max(3,Math.min(9,Math.floor(d/65)+2));return{path:`M${Array.from({length:f},(e,t)=>{let n=a+(s-a)*t/(f-1),r=l+(u-l)*t/(f-1);return`${48+n/100*G(p)} ${g(r)}`}).join(` L`)}`,projected:n,safe:n<=100}}),D=P(()=>{if(G(b).length===0)return`NO OBSERVATIONS`;if(G(b).length===1)return`ONE OBSERVATION`;let e=Math.floor(G(x)/6e4);if(e<60)return`${e} MIN CONTINUOUS`;let t=Math.floor(e/60);return t<24?`${t}H CONTINUOUS`:`${Math.floor(t/24)}D ${t%24}H CONTINUOUS`});var ee=Pa(),te=z(ee),O=R(te);let ne;var k=R(O),re=R(k),ie=V(re);N(k);var ae=V(k),oe=V(ae);Z(oe,17,()=>s,qr,(e,t)=>{var n=Ea(),r=z(n),i=V(r),a=B(i);H(()=>{Q(r,`x1`,48+G(t)/100*G(p)),Q(r,`x2`,48+G(t)/100*G(p)),Q(r,`y2`,G(f)),Q(i,`x`,48+G(t)/100*G(p)),Q(i,`y`,G(f)+20),Y(a,`${G(t)??``}%`)}),J(e,n)});var se=V(oe);Z(se,17,()=>s,qr,(e,t)=>{var n=Da(),r=z(n),i=V(r),a=B(i);H((e,n,o)=>{Q(r,`y1`,e),Q(r,`x2`,G(d)),Q(r,`y2`,n),Q(i,`y`,o),Y(a,`${G(t)??``}%`)},[()=>g(G(t)),()=>g(G(t)),()=>g(G(t))+4]),J(e,n)});var ce=V(se),le=V(ce),ue=V(le),de=e=>{var t=Oa(),n=z(t),i=V(n),a=V(i),o=B(R(a));N(a),H((e,t)=>{Q(n,`d`,G(y)),Q(i,`d`,G(y)),Q(a,`cx`,48+r()[0].elapsed/100*G(p)),Q(a,`cy`,e),Y(o,`First observation: ${t??``}`)},[()=>g(r()[0].used),()=>ha(r()[0].at)]),J(e,t)};X(ue,e=>{G(a)&&r().length&&e(de)});var fe=V(ue),pe=e=>{var t=ka(),r=z(t),i=V(r),a=B(R(i));N(i),H(e=>{Q(r,`d`,G(E).path),Q(i,`d`,G(E).path),Q(i,`marker-mid`,`url(#${n}-trend-arrow)`),Y(a,`${G(o)===`window`?`Window-average pace`:`Recent consumption trajectory`} projects ${e??``}% at reset if continued`)},[()=>G(E).projected.toFixed(1)]),J(e,t)};X(fe,e=>{G(E)&&e(pe)});var A=V(fe,2),me=V(A),he=V(me),ge=B(R(he));N(he),N(O),N(te);var _e=V(te,2),ve=R(_e),j=R(ve),ye=V(j,3),M=B(ye,!0),be=V(ye,2),xe=e=>{var t=Aa(),n=B(V(z(t)));H(e=>Y(n,`TREND // ${e??``}`),[()=>G(E).projected>=100?`QUOTA EXHAUSTION PROJECTED`:`${G(E).projected.toFixed(1)}% PROJECTED AT RESET`]),J(e,t)};X(be,e=>{G(E)&&e(xe)}),N(ve);var Ce=V(ve,2),we=R(Ce),Te=R(we);Si(Te),Se(),N(we);var Ee=V(we,2),De=V(R(Ee),2),Oe=R(De);Oe.value=Oe.__value=`off`;var ke=V(Oe),Ae=B(ke);ke.value=ke.__value=`halfHour`;var je=V(ke),Me=B(je);je.value=je.__value=`hour`;var Ne=V(je),Pe=B(Ne);Ne.value=Ne.__value=`day`;var Fe=V(Ne),Ie=B(Fe);Fe.value=Fe.__value=`window`,N(De),hi(De),N(Ee);var Le=B(V(Ee,2));N(Ce),N(_e);var Re=V(_e,2),ze=e=>{var t=Na(),n=V(R(t),2),a=e=>{var t=Ma(),n=R(t),i=V(R(n),2);Z(i,21,r,qr,(e,t,n)=>{var r=ja(),i=R(r),a=R(i),o=B(a,!0);N(i);var s=V(i),c=B(s),l=V(s),u=B(l),d=B(V(l),!0);N(r),H((e,r)=>{Q(a,`datetime`,G(t).at),Y(o,e),Y(c,`${r??``}%`),Y(u,`${G(t).used??``}%`),Y(d,n===0?`First observation`:G(t).break?`Gap before this observation`:`Connected to previous observation`)},[()=>ha(G(t).at),()=>G(t).elapsed.toFixed(1)]),J(e,r)}),N(i),N(n),N(t),J(e,t)};X(n,e=>{G(i)&&e(a)}),N(t),Ri(`open`,`toggle`,t,e=>L(i,e),()=>G(i)),J(e,t)};X(Re,e=>{r().length&&e(ze)}),H((e,r,i,a,o,s)=>{Q(O,`viewBox`,`0 0 ${G(l)} ${G(u)}`),Q(O,`aria-label`,e),ne=di(O,``,ne,{"--trend-color":G(E)?.safe?`#115b35`:`#7f1825`}),Q(re,`id`,n),Q(ie,`id`,n+`-trend-arrow`),Q(ae,`width`,G(p)),Q(ae,`height`,G(m)),Q(ae,`fill`,`url(#${n})`),Q(ce,`d`,`M48 20 V${G(f)} H${G(d)}`),Q(le,`y1`,r),Q(le,`x2`,G(d)),Q(le,`y2`,i),Q(A,`x`,48+G(p)/2),Q(A,`y`,G(u)-5),Q(me,`cx`,G(h)),Q(me,`cy`,G(_)),Q(he,`cx`,G(h)),Q(he,`cy`,G(_)),Y(ge,`${t.used??``}% consumed // ${a??``}% of period elapsed`),Y(j,`${t.used??``}% USED // ${o??``}% TIME ELAPSED`),Y(M,s),ke.disabled=!G(C).halfHour,Y(Ae,`LAST 30 MINUTES${G(C).halfHour?``:` — UNAVAILABLE`}`),je.disabled=!G(C).hour,Y(Me,`LAST HOUR${G(C).hour?``:` — UNAVAILABLE`}`),Ne.disabled=!G(C).day,Y(Pe,`LAST 24 HOURS${G(C).day?``:` — UNAVAILABLE`}`),Fe.disabled=!G(C).window,Y(Ie,`FROM WINDOW START${G(C).window?``:` — NO TIME ELAPSED`}`),Y(Le,`HISTORY // ${G(D)??``}`)},[()=>`Pace: ${t.used}% consumed, ${t.elapsed.toFixed(1)}% of quota period elapsed. ${G(v)>0?`Above`:G(v)<0?`Below`:`On`} the steady-consumption line.${G(a)&&r().length?` Trace path contains ${r().length} ${r().length===1?`observation`:`observations`}; gaps are not interpolated.`:``}`,()=>g(0),()=>g(100),()=>t.elapsed.toFixed(1),()=>t.elapsed.toFixed(1),()=>Math.abs(G(v))<.05?`ON PACE`:G(v)>0?`ABOVE THE LINE — CONSUMING FASTER THAN TIME`:`BELOW THE LINE — WITHIN PACE`]),Fi(te,`clientWidth`,e=>L(l,e)),Fi(te,`clientHeight`,e=>L(u,e)),Ai(Te,()=>G(a),e=>L(a,e)),gi(De,()=>G(o),e=>L(o,e)),J(e,ee),Ge()}var Ia=q(` `),La=q(`

      Quota refresh failed. Values below are the last successful observation.

      `),Ra=q(`

      `),za=q(`

      Expiry details unavailable. No listed expiry does not mean no expiry.

      `),Ba=q(`

      `),Va=q(`

      Read-only preview. Use the terminal to redeem a reset.

      The backend may return only some credits. This list does not establish - redemption order.

      `),Ha=q(` `,1),Ua=Mr(``),Wa=Mr(``),Ga=q(`
      `),Ka=q(`

      Cycle duration or reset date unavailable — position cannot be - plotted.

      `),qa=q(`
      EMPTYFULL
      `),Ja=q(`

      `,1),Ya=q(`
      `,1),Xa=q(`

      `),Za=q(`

      `),Qa=q(`

      No quota windows reported yet.

      `),$a=q(`
      `,1),eo=q(`

      All reported windows are shown. API-equivalent learning and quota status - scoring remain in the terminal for this first preview.

      `,1),to=q(` `,1);function no(e,t){We(t,!0);let n=Gi(t,`params`,19,()=>({})),r=P(()=>ta.filter(e=>e!==`thresholds`||!!$.data?.thresholds?.length)),i=I(Xt(Date.now())),a=P(()=>G(r).includes(na(n().view))?na(n().view):`bars`);Ki(()=>{let e=setInterval(()=>L(i,Date.now(),!0),1e3);return()=>clearInterval(e)});function o(e){return!e.duration||e.duration<=0||!e.reset?null:Math.max(0,Math.min(100,100*(1-(e.reset*1e3-G(i))/(e.duration*6e4))))}yn(()=>{oa.view=G(a),(n().view===`zone`||n().view===`consumption-pace`)&&Qi(`/quota/pace`)});function s(e){let t=e/100*Math.PI*2;return`M60 60 L60 14 A46 46 0 ${+(e>50)} 1 ${60+46*Math.sin(t)} ${60-46*Math.cos(t)} Z`}var c=to(),l=z(c);Z(l,21,()=>G(r),qr,(e,t)=>{var n=Ia();let r;var i=B(n,!0);H(e=>{Q(n,`href`,`#/quota/`+G(t)),Q(n,`aria-current`,G(a)===G(t)?`page`:void 0),r=li(n,1,``,null,r,{active:G(a)===G(t)}),Y(i,e)},[()=>G(t)===`fuel`?`FUEL TANK`:G(t).toUpperCase()]),J(e,n)}),N(l);var u=V(l,2),d=e=>{var t=eo(),n=z(t),r=e=>{J(e,La())};X(n,e=>{$.data.quotaError&&e(r)});var i=V(n,2),c=e=>{var t=Ra(),n=B(t);H(()=>Y(n,`Last usage accounting issue: - ${$.data.usageDiagnostic??``}`)),J(e,t)};X(i,e=>{$.data.usageDiagnostic&&e(c)});var l=V(i,2),u=B(l),d=V(l,2),f=e=>{Ta(e,{})},p=e=>{var t=Va(),n=R(t),r=B(n),i=V(n,4),a=e=>{J(e,za())};X(i,e=>{$.data.credits.length||e(a)}),Z(V(i,2),17,()=>$.data.credits,qr,(e,t)=>{var n=Ba(),r=R(n),i=B(r),a=B(V(r,2),!0);N(n),H(e=>{Y(i,`${(G(t).title||`Quota reset`)??``} // ${G(t).status??``}`),Y(a,e)},[()=>G(t).expiryKnown?G(t).expires?`EXPIRES `+ha(G(t).expires):`Does not expire`:`Expiry information unavailable`]),J(e,n)}),Se(2),N(t),H(()=>Y(r,`RESET INVENTORY // ${$.data.creditCount??``} AVAILABLE`)),J(e,t)},m=e=>{var t=$a(),n=z(t);let r;Z(n,21,()=>$.data.meters,qr,(e,t)=>{let n=P(()=>o(G(t)));var r=Za(),i=R(r),c=B(i,!0),l=V(i,2),u=R(l),d=e=>{var n=Ha(),r=z(n),i=B(r),a=B(V(r));H(()=>{Y(i,`FREE ${100-G(t).used}%`),Y(a,`USED ${G(t).used??``}%`)}),J(e,n)},f=e=>{var n=Ha(),r=z(n),i=B(r),a=B(V(r));H(()=>{Y(i,`USED ${G(t).used??``}%`),Y(a,`FREE ${100-G(t).used}%`)}),J(e,n)};X(u,e=>{G(a)===`fuel`?e(d):e(f,-1)}),N(l);var p=V(l,2);let m;var h=R(p),g=e=>{var n=Ga(),r=R(n),i=V(R(r)),a=e=>{J(e,Ua())},o=e=>{var n=Wa();H(e=>Q(n,`d`,e),[()=>s(G(t).used)]),J(e,n)};X(i,e=>{G(t).used>=100?e(a):G(t).used>0&&e(o,1)}),N(r),N(n),H(()=>Q(r,`aria-label`,`${G(t).used}% quota used`)),J(e,n)},_=e=>{var r=Pr(),i=z(r),a=e=>{{let r=P(()=>G(t).trail||[]);Fa(e,{get used(){return G(t).used},get elapsed(){return G(n)},get trail(){return G(r)},get duration(){return G(t).duration}})}},o=e=>{J(e,Ka())};X(i,e=>{G(n)===null?e(o,-1):e(a)}),J(e,r)},v=e=>{var r=Ya(),i=z(r),o=R(i);let s;N(i);var c=V(i,2),l=e=>{J(e,qa())};X(c,e=>{G(a)===`fuel`&&e(l)});var u=V(c,2),d=e=>{var t=Ja(),r=z(t),i=B(r),o=V(r,2),s=R(o);let c;N(o),H(e=>{Y(i,`RESET CYCLE // ${e??``}% ELAPSED`),c=di(s,``,c,{width:`${G(a)===`fuel`?100-G(n):G(n)}%`})},[()=>Math.floor(G(n))]),J(e,t)};X(u,e=>{G(n)!==null&&e(d)}),H(()=>{Q(i,`aria-label`,G(a)===`fuel`?`Fuel remaining`:`Quota used`),Q(i,`aria-valuenow`,G(a)===`fuel`?100-G(t).used:G(t).used),s=di(o,``,s,{width:`${G(a)===`fuel`?100-G(t).used:G(t).used}%`})}),J(e,r)};X(h,e=>{G(a)===`pie`?e(g):G(a)===`pace`?e(_,1):e(v,-1)}),N(p);var y=V(p,2),b=B(y),x=V(y,2),S=e=>{var n=Xa(),r=B(n,!0);H(()=>Y(r,G(t).details)),J(e,n)};X(x,e=>{G(t).details&&e(S)}),N(r),H(e=>{Y(c,G(t).name),m=li(p,1,`meter-graphic`,null,m,{"bar-graphic":G(a)===`bars`||G(a)===`fuel`}),Y(b,`RESETS // ${e??``}`)},[()=>ha(G(t).reset)]),J(e,r)}),N(n);var i=V(n,2),c=e=>{J(e,Qa())};X(i,e=>{$.data.meters.length||e(c)}),H(()=>r=li(n,1,`quota-grid`,null,r,{radial:G(a)===`pie`,zone:G(a)===`pace`})),J(e,t)};X(d,e=>{G(a)===`thresholds`?e(f):G(a)===`resets`?e(p,1):e(m,-1)}),Se(2),H(e=>Y(u,`QUOTA // OBSERVED ${e??``}`),[()=>ha($.data.quotaAt)]),J(e,t)};X(u,e=>{$.data&&e(d)}),J(e,c),Ge()}var ro=q(`
      `),io=q(`

      `,1);function ao(e,t){We(t,!0);let n=Gi(t,`values`,19,()=>[]),r=Gi(t,`label`,3,`Token activity`),i=Gi(t,`capacity`,3,0),a=P(()=>Math.max(0,...n())),o=P(()=>i()>n().length?[...Array(i()-n().length).fill(0),...n()]:n());var s=io(),c=z(s),l=B(c),u=V(c,2);Z(u,21,()=>G(o),qr,(e,t)=>{var n=ro();let r;H((e,t)=>{Q(n,`title`,e),r=di(n,``,r,{height:t})},[()=>G(t).toLocaleString(`en-GB`)+` tokens`,()=>`${100*G(t)/Math.max(1,G(a))}%`]),J(e,n)}),N(u),H((e,t)=>{Y(l,`SCALE // 0 — ${e??``} TOKENS`),Q(u,`aria-label`,t)},[()=>G(a).toLocaleString(`en-GB`),()=>`${r()}. Peak ${G(a).toLocaleString(`en-GB`)} tokens.`]),J(e,s),Ge()}function oo(e){if(!e||![`LAST REPLY`,`LAST ACTIVITY`].includes(e.contextKind))return``;let t=e.text||e.contextKind===`LAST ACTIVITY`&&e.workingCommand||``;return t.trim()?t:``}var so=q(` `),co=q(``),lo=q(`

      `),uo=q(`

      `),fo=q(`
      `),po=q(`

      `),mo=q(`

      `,1),ho=q(`

      `),go=q(`

      Loading / sending…

      `),_o=q(`
      `),vo=q(`

      `),yo=q(`

       
      `),bo=q(`
      `),xo=q(`
      `),So=q(`

      Review expired. Press Enter to review again.

      `),Co=q(`

      `,1),wo=q(`

      `,1),To=q(`

      No available matching commands.

      `),Eo=q(` `,1),Do=q(`

      `),Oo=q(`

      Review expired. Review the option again.

      `),ko=q(``),Ao=q(``),jo=q(``),Mo=q(``),No=q(``),Po=q(``),Fo=q(`
      `),Io=q(`
      `);function Lo(e,t){let n=Fr();We(t,!0);let r=e=>{var t=Pr();Z(z(t),19,()=>G(A),e=>e.id,(e,t,r)=>{var i=co();let o;var s=R(i),c=B(s,!0),l=V(s),u=B(l),d=V(l),f=e=>{var n=so(),r=B(n,!0);H(()=>Y(r,G(t).help)),J(e,n)};X(d,e=>{(G(v)?.path===``||G(le))&&e(f)}),N(i),H((e,s)=>{Q(i,`id`,`${n}-${G(r)}`),Q(i,`role`,G(le)?`option`:void 0),Q(i,`aria-selected`,G(le)?G(T)===G(r):void 0),Q(i,`tabindex`,G(le)?-1:void 0),i.disabled=G(g)||G(_e)&&!a(),Q(i,`title`,G(t).help),Q(i,`aria-label`,e),o=li(i,1,`svelte-7bfwr9`,null,o,{suggested:G(T)===G(r)}),Y(c,G(t).label),Y(u,`${G(le)&&G(t).selected?` // CURRENT`:``}${s??``}`)},[()=>G(t).label+(G(le)&&G(t).selected?` // CURRENT`:``)+ae(G(t)),()=>ae(G(t))]),Sr(`focus`,i,()=>{G(le)||j(G(r))}),K(`click`,i,()=>{j(G(r)),xe(G(t))}),J(e,i)}),J(e,t)},i=Gi(t,`query`,3,``),a=Gi(t,`localOnly`,3,!1),o=Gi(t,`oncopy`,3,()=>{}),s=Gi(t,`onchange`,3,()=>{}),c=Gi(t,`oncomplete`,3,e=>{}),l=[`statusline`,`usage`,`copy`,`theme`,`exit`,`quit`];function u(){return[{id:`local-statusline`,label:`/statusline`,help:`Choose fields for Codexometer’s detail footer.`,next:`statusline`},{id:`local-usage`,label:`/usage`,help:`Open Codexometer’s usage history. Quota and reset views remain available.`,next:`usage`},{id:`local-copy`,label:`/copy`,help:`Copy the selected session’s observed reply or activity with its Copy control.`,next:`copy`},{id:`local-theme`,label:`/theme`,help:`Open the existing Codexometer theme selector. Saved browser display preferences only.`,next:`theme`},{id:`local-exit`,label:`/exit`,help:`Leave session detail and return to Sessions. The local server remains running.`,next:`exit`},{id:`local-quit`,label:`/quit`,help:`Alias for /exit: return to Sessions. The local server remains running.`,next:`quit`}]}function d(){return{title:`/ COMMANDS`,help:`Local Codexometer commands remain available without session control.`,path:``,revision:``,choices:u()}}function f(){L(b,``),L(O,``),L(x,0),L(ne,[],!0)}function p(){w++,L(g,!1),L(h,!1),L(E,i()),f(),L(_,``),G(fe)?s()():c()(i())}async function m(e){if(!l.includes(e))return!1;if(e===`copy`&&!oo($.data?.sessions.find(e=>e.id===t.session)))return L(_,`No reply or activity text is available to copy for this session.`),!0;if(e===`copy`){let e=o()();p(),await e}else p(),e===`usage`?location.hash=`/usage`:e===`theme`?ca.requests++:e===`statusline`?sa.open=!0:location.hash=`/sessions`;return!0}let h=I(!1),g=I(!1),_=I(``),v=I(null),y=I(null),b=I(``),x=I(0),S=I(Xt(Date.now())),C=I(``),w=0,T=I(0),E=I(null),D=I(void 0),ee=I(void 0),te=I(void 0),O=I(``),ne=I(Xt([]));function k(e){return[`rename/`,`cd/`,`goal/set/`,`goal/edit/`,`goal/budget/`].some(t=>e.startsWith(t))}let re=P(()=>k(G(v)?.path||``)),ie=P(()=>!!G(y)&&!G(re)||!!G(O)||G(ne).length>0);function ae(e){return e.unavailable?e.unavailable.startsWith(`Waiting for idle.`)?` // WAITING FOR IDLE`:` // UNAVAILABLE`:e.next?` →`:e.action?``:` // HELP`}async function oe(e){G(v)&&!(G(ve)&&G(pe))&&L(ne,[...G(ne),{path:G(v).path,index:G(T),input:G(C)}],!0),await be(e)}async function se(){if(!G(ie))return;if(w++,L(g,!1),L(b,``),L(x,0),L(_,``),G(O)){L(O,``),G(D)?.focus();return}if(G(y)&&!G(re)){L(y,null),await ur(),G(D)?.focus();return}let e=G(ne).at(-1);e&&(L(ne,G(ne).slice(0,-1),!0),await be(e.path,e))}function ce(){G(v)&&G(C).trim()&&!G(g)&&!G(_e)&&oe(G(v).path+`/`+encodeURIComponent(G(C).trim()))}let le=P(()=>G(h)&&!!G(v)?.picker&&!G(y)),ue=P(()=>G(h)&&!G(v)?.input&&!G(y)),de=setInterval(()=>{L(S,Date.now(),!0)},1e3);qi(()=>{clearInterval(de),w++});let fe=P(()=>i().trim().startsWith(`/`)&&!i().trim().startsWith(`//`)),pe=P(()=>G(v)?.path===``&&G(fe)?i().trim().slice(1).toLowerCase():``),A=P(()=>(G(v)?.choices||[]).filter(e=>!G(pe)||e.label.toLowerCase().startsWith(`/`+G(pe)))),me=P(()=>G(y)||G(A)[G(T)]),he=P(()=>!!G(b)&&G(S)G(A).find(e=>e.id===G(O))),_e=P(()=>!$.connected||!!$.data?.sessionsError),ve=P(()=>G(h)&&G(fe)&&(!G(v)||G(v).path===``)&&!G(y));yn(()=>{i(),L(T,0)}),yn(()=>{let e=G(T);G(A),G(ue)&&ur().then(()=>{let t=G(D)?.children[e];if(!t||!G(D))return;let n=G(D).getBoundingClientRect(),r=t.getBoundingClientRect();r.topn.bottom&&(G(D).scrollTop+=r.bottom-n.bottom)})});function j(e){G(T)!==e&&G(v)?.picker&&(L(O,``),L(b,``),L(x,0),L(_,``),w++),L(T,e,!0)}function ye(){w++,L(g,!1),L(h,!1),L(E,i()),f(),c()(i())}function M(e){let t=!!G(ee)?.contains(e.target);if(!G(h)||!G(ue)&&!G(y)&&!G(v)?.input||!G(fe)&&!t||G(_e)&&!a()||e.isComposing||e.ctrlKey||e.metaKey||e.altKey)return;if(G(v)?.input){e.key===`Escape`&&(e.preventDefault(),e.stopPropagation(),ye());return}let n=e.key===`ArrowLeft`,r=e.key.toLowerCase()===`f`,o=e.key.toLowerCase()===`c`;if((o||r||n)&&!t||![`ArrowUp`,`ArrowDown`,`Tab`,`Enter`,`Escape`].includes(e.key)&&!o&&!r&&!n||e.key===`Tab`&&(G(y)||G(v)?.path!==``||e.shiftKey)||e.key===`Enter`&&e.target instanceof Element&&e.target.closest(`button`)&&!G(D)?.contains(e.target)&&!e.target.closest(`[data-command-confirm]`)||G(y)&&!t||G(y)&&[`ArrowUp`,`ArrowDown`].includes(e.key))return;if(e.preventDefault(),e.stopPropagation(),e.key===`Escape`){ye();return}if(G(g)||e.repeat&&(o||e.key===`Enter`))return;if(n){G(ie)&&se();return}if(r){be(G(v)?.path||``,void 0,!0);return}if(o){(G(y)?.action||G(le)&&G(O))&&Ce();return}if(G(y)){e.key===`Enter`&&G(y).action&&!G(he)&&!G(_)&&Se(G(y));return}if(e.key===`ArrowUp`||e.key===`ArrowDown`){j(Math.min(Math.max(G(T)+(e.key===`ArrowUp`?-1:1),0),Math.max(G(A).length-1,0))),!G(le)&&G(D)?.contains(e.target)&&ur().then(()=>(G(D)?.children[G(T)])?.focus());return}if(e.key===`Enter`&&(G(v)?.path===``||G(v)?.path===`goal`)&&/^\/goal\s+\S/.test(i().trim())){let e=G(v)?.path===`goal`?G(A).find(e=>i().trim().slice(6).trim().toLowerCase()===e.label.toLowerCase()):void 0;if(e){xe(e);return}oe(i().trim().slice(1));return}let s=G(A)[G(T)];s&&(e.key===`Tab`?c()(s.label):G(le)?Se(s):xe(s))}yn(()=>{G(_e)&&!a()&&(w++,L(g,!1),f(),G(h)&&(L(h,!1),L(E,i())),L(v,null),L(y,null))}),yn(()=>{G(fe)&&!G(h)&&i()!==G(E)&&(!G(_e)||a())&&(f(),L(h,!0),be(``))});async function be(e,n,r=!1){let i=++w;if(G(v)&&L(v,{...G(v),path:e},!0),n&&L(C,n.input,!0),L(g,!0),L(y,null),L(O,``),L(b,``),L(x,0),L(_,``),a())L(v,d(),!0),L(T,n?.index??0,!0),L(g,!1);else try{let a=await ya(`commands`,{session:t.session,command:{mode:`list`,path:e,...r?{refresh:!0}:{}}});i===w&&((e===``||e===`help`)&&(a.choices=[...a.choices.filter(e=>!l.includes(e.label.replace(/^\//,``))),...u()]),L(v,a,!0),L(T,n?Math.min(n.index,Math.max(a.choices.length-1,0)):a.picker?Math.max(a.choices.findIndex(e=>e.selected),0):0,!0),a.picker&&ur().then(()=>G(D)?.focus()),L(C,n?.input??a.value??``,!0),n&&ur().then(()=>{a.input?G(ee)?.querySelector(`input`)?.focus():G(D)?.focus()}),k(a.path)&&a.choices.length===1&&(L(g,!1),xe(a.choices[0])))}catch{i===w&&(L(_,`Command catalogue unavailable. Use Codex.`),e===``||e===`help`?(L(v,d(),!0),L(_,``)):L(v,{title:`/`+e,help:``,path:e,revision:``,choices:[]},!0))}finally{i===w&&L(g,!1)}}async function xe(e){e.unavailable||G(g)||e.next&&await m(e.next)||G(_)||(G(v)?.picker?(j(G(A).findIndex(t=>t.id===e.id)),e.action&&(e.reversible||$.data?.noConfirm)?await Se(e):G(D)?.focus()):e.next?await oe(e.next):await Se(e))}async function Se(e){if(G(g)||G(_e)||!G(v)||e.unavailable||G(_))return;let n=e.action&&(e.reversible||$.data?.noConfirm);if(G(v).picker){if(G(he)&&G(O)===e.id)return;L(O,e.id,!0)}else n||(L(y,e,!0),ur().then(()=>G(te)?.focus()));if(L(b,``),L(x,0),L(_,``),!e.action)return;let r=++w;L(g,!0);try{let n=await ya(`commands`,{session:t.session,command:{mode:`prepare`,path:G(v).path,revision:G(v).revision,choice:e.id}});if(r===w){let e=Date.parse(n.expires);if(!n.confirmation||!Number.isFinite(e)||Date.now()>=e)throw Error(`Review confirmation unavailable`);L(b,n.confirmation,!0),L(x,e,!0)}}catch{r===w&&L(_,`Option changed or unavailable. Refresh before retrying.`)}finally{r===w&&L(g,!1)}n&&r===w&&G(b)&&await Ce()}async function Ce(){if(!G(b)||G(_e)||G(g)||Date.now()>=G(x)||!G(v)||G(v).picker&&G(O)!==G(A)[G(T)]?.id)return;let e=G(b);L(b,``),L(g,!0);let n=++w;try{let r=await ya(`commands`,{session:t.session,confirmation:e,command:{mode:`commit`,path:G(v).path}});n===w&&(L(_,r.message,!0),L(h,!1),L(v,null),L(y,null),f(),L(E,null),s()())}catch{n===w&&L(_,`Change unconfirmed. Check Codex before retrying.`)}finally{n===w&&L(g,!1)}}var we={handleKey:M},Te=Io(),Ee=R(Te),De=V(Ee,2),Oe=e=>{var t=lo(),n=B(t,!0);H(()=>Y(n,G(_))),J(e,t)};X(De,e=>{G(_)&&!G(h)&&e(Oe)});var ke=V(De,2),Ae=e=>{var i=Fo();let o;var s=R(i),c=e=>{var t=mo(),n=z(t),r=B(n,!0),i=V(n,2),a=e=>{var t=fo();Z(t,21,()=>G(v).help.split(` -`),qr,(e,t)=>{var n=uo(),r=B(n,!0);H(()=>{Q(n,`title`,G(t)),Y(r,G(t))}),J(e,n)}),N(t),J(e,t)},o=e=>{var t=po(),n=B(t,!0);H(()=>Y(n,G(v)?.help||`Loading live options…`)),J(e,t)};X(i,e=>{G(v)?.path===`goal`?e(a):e(o,-1)}),H(()=>Y(r,G(v)?.title||`/ COMMANDS`)),J(e,t)};X(s,e=>{G(ve)||e(c)});var l=V(s,2),u=e=>{var t=ho(),n=B(t,!0);H(()=>Y(n,G(_))),J(e,t)};X(l,e=>{G(_)&&e(u)});var d=V(l,2),f=e=>{J(e,go())};X(d,e=>{G(g)&&e(f)});var p=V(d,2),m=e=>{var t=_o(),r=R(t),i=R(r),o=V(i);Si(o),N(r),N(t),H(e=>{Q(t,`id`,`${n}-input`),Y(i,`${e??``} `),Q(o,`aria-label`,G(v).inputLabel||`Session name`),Q(o,`maxlength`,G(v).inputLimit||512),o.disabled=G(g)||G(_e)&&!a()},[()=>(G(v).inputLabel||`Session name`).toUpperCase()]),Sr(`submit`,t,e=>{e.preventDefault(),ce()}),Di(o,()=>G(C),e=>L(C,e)),J(e,t)},h=e=>{var n=yo(),r=R(n),i=B(r,!0),a=V(r,2),o=B(a,!0),s=V(a,2),c=e=>{var n=vo(),r=R(n),i=V(r),a=e=>{J(e,Nr(`Only this session's saved - name changes.`))},o=P(()=>G(v)?.path.startsWith(`rename/`)),s=e=>{J(e,Nr(`Only this session’s - directory changes; project configuration is not reloaded.`))},c=P(()=>G(v)?.path.startsWith(`cd/`)),l=e=>{J(e,Nr(`Only this session's goal - changes.`))},u=P(()=>G(v)?.path.startsWith(`goal/`)),d=e=>{J(e,Nr(`No change sent yet. Model, reasoning and speed settings can - change token usage and cost. Changes affect subsequent turns of - this session, not global defaults. Automatic quota thresholds - may later supersede model settings.`))};X(i,e=>{G(o)?e(a):G(c)?e(s,1):G(u)?e(l,2):e(d,-1)}),N(n),H(()=>Y(r,`TARGET // ${t.session??``}. `)),J(e,n)};X(s,e=>{G(y).action&&e(c)}),N(n),Li(n,e=>L(te,e),()=>G(te)),H(()=>{Y(i,G(y).label),Y(o,G(y).help||`No additional help supplied by Codex.`)}),J(e,n)},w=e=>{var t=Eo(),i=z(t),a=e=>{var t=bo(),i=R(t);r(i),N(t),Li(t,e=>L(D,e),()=>G(D)),H(()=>Q(t,`aria-activedescendant`,`${n}-${G(T)}`)),J(e,t)},o=e=>{var t=xo();let n;var i=R(t);r(i),N(t),Li(t,e=>L(D,e),()=>G(D)),H(()=>n=li(t,1,`command-options svelte-7bfwr9`,null,n,{vertical:G(ve)})),J(e,t)};X(i,e=>{G(le)?e(a):e(o,-1)});var s=V(i,2),c=e=>{var t=wo(),n=z(t),r=B(n,!0),i=V(n,2),a=e=>{var t=Co(),n=z(t),r=B(n),i=V(n,2),a=e=>{J(e,So())};X(i,e=>{G(b)&&G(S)>=G(x)&&e(a)}),H(()=>Y(r,`REVIEW // ${G(ge).label??``}. No change sent yet.`)),J(e,t)};X(i,e=>{G(ge)&&e(a)}),H(()=>Y(r,G(A)[G(T)]?.help)),J(e,t)};X(s,e=>{G(le)&&e(c)});var l=V(s,2),u=e=>{J(e,To())};X(l,e=>{!G(g)&&!G(A).length&&e(u)}),J(e,t)};X(p,e=>{G(v)?.input?e(m):G(y)?e(h,1):e(w,-1)});var E=V(p,2),O=e=>{var t=Do(),n=B(t,!0);H(()=>Y(n,G(me).unavailable)),J(e,t)};X(E,e=>{G(me)?.unavailable&&e(O)});var ne=V(E,2),k=e=>{J(e,Oo())};X(ne,e=>{G(b)&&G(S)>=G(x)&&!G(le)&&e(k)});var re=V(ne,2),ae=R(re),oe=e=>{var t=Pr(),r=z(t),i=e=>{var t=ko(),r=B(t,!0);H(e=>{Q(t,`form`,`${n}-input`),t.disabled=e,Y(r,G(v).reversibleInput||$.data?.noConfirm?`ENTER APPLY`:`ENTER REVIEW`)},[()=>G(g)||G(_e)||!G(C).trim()]),J(e,t)},o=e=>{var t=Pr(),n=z(t),r=e=>{var t=Ao();H(()=>t.disabled=G(g)||G(_e)&&!a()),K(`click`,t,Ce),J(e,t)},i=e=>{var t=jo(),n=B(t,!0);H(()=>{t.disabled=G(g)||G(_e)&&!a(),Y(n,G(me).reversible||$.data?.noConfirm?`ENTER APPLY`:`ENTER REVIEW`)}),K(`click`,t,()=>{G(me)&&Se(G(me))}),J(e,t)};X(n,e=>{G(he)&&!G(me).reversible&&!$.data?.noConfirm?e(r):e(i,-1)}),J(e,t)},s=e=>{var t=jo(),n=B(t);H(()=>{t.disabled=G(g)||G(_e)&&!a(),Y(n,`ENTER ${G(me).next?`OPEN`:`HELP`}`)}),K(`click`,t,()=>{G(me)&&xe(G(me))}),J(e,t)};X(r,e=>{G(v)?.input?e(i):G(me)?.action&&!G(me).unavailable?e(o,1):!G(y)&&G(me)&&!G(me).unavailable&&e(s,2)}),J(e,t)};X(ae,e=>{G(_)||e(oe)});var ue=V(ae,2),de=e=>{var t=Mo();H(()=>t.disabled=G(g)),K(`click`,t,se),J(e,t)};X(ue,e=>{G(ie)&&e(de)});var fe=V(ue,2),pe=e=>{var t=No();H(()=>t.disabled=G(g)),K(`click`,t,()=>be(G(v)?.path||``,void 0,!0)),J(e,t)};X(fe,e=>{G(ve)||e(pe)});var j=V(fe,2);N(re);var we=V(re,2),Te=e=>{var t=Po(),n=B(t);H(()=>Y(n,`↑/↓ select${G(ve)?` · Tab complete`:``}`)),J(e,t)};X(we,e=>{!G(v)?.input&&!G(y)&&e(Te)}),N(i),Li(i,e=>L(ee,e),()=>G(ee)),H(()=>{Q(i,`aria-label`,G(ve)?`Slash command suggestions`:`Session command options`),o=li(i,1,`svelte-7bfwr9`,null,o,{"command-popup":G(ve)})}),K(`keydown`,i,M),K(`click`,j,ye),J(e,i)};return X(ke,e=>{G(h)&&e(Ae)}),N(Te),H(()=>Ee.disabled=G(_e)&&!a()||G(g)),K(`click`,Ee,()=>{L(h,!G(h)),L(E,G(h)?null:i(),!0),L(b,``),G(h)?(L(ne,[],!0),be(``)):ye()}),J(e,Te),Ge(we)}Cr([`click`,`keydown`]);var Ro=q(` `,1),zo=q(`
      `),Bo=q(`
      /
      `);function Vo(e,t){We(t,!0);let n=P(()=>t.lines.filter(e=>e.kind===`addition`).length),r=P(()=>t.lines.filter(e=>e.kind===`removal`).length);var i=Bo(),a=R(i),o=R(a),s=B(o),c=B(V(o,2));N(a),Z(V(a,2),17,()=>t.lines,qr,(e,t)=>{var n=zo();let r;var i=R(n),a=e=>{var n=Ro(),r=z(n),i=B(r,!0),a=B(V(r),!0);H(()=>{Y(i,G(t).old||``),Y(a,G(t).new||``)}),J(e,n)};X(i,e=>{G(t).kind!==`heading`&&G(t).kind!==`metadata`&&e(a)});var o=B(V(i),!0);N(n),H(()=>{r=li(n,1,`diff-line svelte-x4gtb6`,null,r,{added:G(t).kind===`addition`,removed:G(t).kind===`removal`,heading:G(t).kind===`heading`}),Y(o,G(t).text)}),J(e,n)}),N(i),H(()=>{Q(a,`aria-label`,`${G(n)} lines added, ${G(r)} lines removed`),Y(s,`+${G(n)??``}`),Y(c,`−${G(r)??``}`)}),J(e,i),Ge()}var Ho=q(`

      `),Uo=q(`

      Send this saved prompt now? This starts Codex work and consumes quota.

      `,1),Wo=q(``),Go=q(`

       

      In memory only. Follows session model, reasoning and speed. Closing - Codexometer cancels pending work.

      `),Ko=q(`

      CURRENT PROFILE

      MODEL / REASONING LEVEL / SPEED

       

      PROPOSED PROFILE

      MODEL / REASONING LEVEL / SPEED

       

      Applied settings remain after Codexometer closes.

      `,1),qo=q(`

      `),Jo=q(`

       
      `,1),Yo=q(`

      Command unavailable from this observation. Open Codex to inspect the - request.

      `),Xo=q(`

      Session controls temporarily unavailable. Check Codex for current state.

      `),Zo=q(`

      Checking session controls…

      `),Qo=q(`

      `),$o=q(`
      About browser controls

      Controls require a supported live request from a connected shared - app-server session. Local observations alone cannot provide them.

      `),es=q(` `,1),ts=q(` `),ns=q(`Grants permission beyond this one action. Check the scope - carefully.`),rs=q(``),is=q(``),as=q(``),os=q(``),ss=q(`

      Not before this time; when quota and idle allow.

      `),cs=q(`

      `,1),ls=q(`

      The existing trigger remains active until saved or deleted. If - it changes or sends while you edit, saving is blocked.

      `),us=q(` `,1),ds=q(``),fs=q(``),ps=q(``),ms=q(` `,1),hs=q(`
    • `),gs=q(`
      View fixed choices

      Type one of these choices exactly. Your answer stays masked.

        `),_s=q(` `,1),vs=q(`

        `),ys=q(`

        `,1),bs=q(` `,1),xs=q(``),Ss=q(`

        `,1),Cs=q(`

        `);function ws(e,t){We(t,!0);let n=[],r=Gi(t,`observedCommand`,3,``),i=Gi(t,`observedFileChanges`,19,()=>[]),a=Gi(t,`review`,3,``),o=Gi(t,`suspended`,3,!1),s=Gi(t,`openScheduler`,3,!1),c=Gi(t,`onProtectedChange`,3,e=>{}),l=Gi(t,`oncopy`,3,()=>{}),u=I(null),d=I(Xt([])),f=I(null),p=I(``),m=I(0),h=I(Xt(Date.now())),g=I(!1),_=I(``),v=I(!1),y=I(!1),b=I(``),x=I(!1),S=I(``),C=I(!1),w=I(``),T=I(`now`),E=I(60),D=I(``),ee=I(!1),te=I(``),O=I(Xt([])),ne=P(()=>G(O).find(e=>e.status!==`sent`)),k=I(void 0),re=I(void 0);function ie(e){if(G(x)){ur().then(()=>G(k)?.focus());return}if(e.status!==`pending`||G(g)||!G(u)?.id||G(u).kind!==`prompt`||G(u).questions?.length)return;L(x,!0),L(S,e.id,!0),ur().then(()=>G(k)?.focus()),L(d,[e.text],!0),L(T,e.trigger===`quota`?`quota`:`at`,!0),L(p,``),L(w,``);let t=new Date(e.at);e.trigger!==`quota`&&L(D,new Date(t.getTime()-t.getTimezoneOffset()*6e4).toISOString().slice(0,16),!0)}yn(()=>{s()&&G(ne)&&!G(C)&&G(u)?.id&&G(u).kind===`prompt`&&!G(u).questions?.length&&(L(C,!0),ie(G(ne)))});async function ae(){if(!a())try{L(O,await ya(`schedules`,{session:t.session},j.signal),!0),G(x)&&!G(O).some(e=>e.id===G(S)&&e.status===`pending`)&&L(p,``),G(w)&&!G(O).some(e=>e.id===G(w)&&e.status===`pending`)&&(L(p,``),L(w,``))}catch{}}async function oe(e){try{L(O,await ya(`schedules`,{session:t.session,cancelId:e},j.signal),!0),L(x,!1),L(p,``),L(w,``),L(d,[],!0)}catch(e){L(_,e instanceof Error?e.message:`Unable to cancel trigger.`,!0)}}yn(()=>(c()(G(g)||G(d).some(e=>e.length>0)),()=>c()(!1)));let se=P(()=>$.data?.sessions.find(e=>e.id===t.session)?.status),ce=P(()=>!$.connected||!!$.data?.sessionsError),le=P(()=>a()!==`profile`&&G(u)?.kind===`prompt`&&!G(u).questions?.length&&!!$.data?.profiles?.some(e=>e.session===t.session&&e.pending)),ue=P(()=>!G(ce)&&!G(y)&&!!G(u)?.id&&G(u).kind===`approval`&&G(b)!==G(u).id),de=P(()=>G(ue)?G(u).command:r()),fe=P(()=>!!G(p)&&G(h)G(u)?.questions?.length?G(u).questions:[{text:`Follow-up message`,secret:!1,freeText:!0,options:[]}]),A=P(()=>G(u)?.kind===`approval`||G(u)?.kind===`profile`?G(f)!==null:G(d).length===G(pe).length&&G(d).every((e,t)=>e.trim().length>0&&(G(pe)[t].freeText||G(pe)[t].options?.includes(e)))),me=P(()=>G(u)?.kind===`prompt`&&!G(u).questions?.length&&G(T)!==`now`),he=P(()=>G(T)===`delay`?G(h)+G(E)*6e4:Date.parse(G(D))),ge=P(()=>{if(!G(me))return``;if(G(x)&&!G(O).some(e=>e.id===G(S)&&e.status===`pending`))return`Trigger changed, removed or already sent. Go back and reopen it; your draft has not been saved.`;if(!G(d)[0]?.trim())return`Enter a message to schedule.`;if(G(T)===`quota`)return``;if(!Number.isFinite(G(he))||G(he)<=G(h))return`Choose a future date/time or a delay greater than zero.`;let e=new Date(G(h));return e.setFullYear(e.getFullYear()+1),G(he)>=e.getTime()?`Choose a time within one year.`:``});function _e(e){return new Date(e).toLocaleString([],{timeZoneName:`short`})}function ve(e){if(e.status!==`pending`)return e.status;if(e.trigger===`at`&&Date.parse(e.at)>G(h))return`Due in `+Math.ceil((Date.parse(e.at)-G(h))/6e4)+` min`;let t=G(ce)?`waiting for a fresh connection`:e.waitingReason||(e.canSend?`ready; awaiting dispatch`:`waiting for quota and an eligible idle session`);return(e.trigger===`at`?`Time reached — `:``)+t}yn(()=>{(G(ce)||o()||G(le)||G(h)>=G(m))&&L(p,``)});let j=new AbortController;Ki(()=>{let e=e=>{e.ctrlKey&&e.key.toLowerCase()===`s`&&!a()&&G(ne)&&(e.preventDefault(),ie(G(ne)))};window.addEventListener(`keydown`,e);let n,r=setInterval(()=>{L(h,Date.now(),!0)},1e3);async function i(){await ae();try{let e=await ya(`offer`,{session:t.session,...a()?{review:a()}:{}},j.signal);if(j.signal.aborted)return;L(y,!1),G(u)?.id!==e.id&&(L(p,``),L(f,null),L(d,Array(Math.max(1,e.questions?.length||0)).fill(``),!0)),L(u,e,!0),G(v)&&e.id&&e.id!==G(b)&&(L(_,``),L(v,!1))}catch{j.signal.aborted||(L(u,null),L(y,!0),L(p,``))}finally{j.signal.aborted||(n=setTimeout(i,2e3))}}return i(),()=>{window.removeEventListener(`keydown`,e),j.abort(),clearTimeout(n),clearInterval(r)}});async function ye(e){if(!e&&G(u)?.kind===`prompt`&&!G(u).questions?.length&&G(d)[0]?.trim().startsWith(`/`)&&!G(d)[0]?.trim().startsWith(`//`)){L(_,`Choose a slash command from COMMANDS; use // for literal slash text.`);return}if(!G(u)?.id||G(g)||G(ce)||o()||G(le)||(e?!e.canSend:!G(A)||G(ge)))return;let n=G(u).id;L(g,!0),L(_,``),L(v,!1),L(p,``),L(w,e?.id||``,!0);try{let r=!e&&G(u).kind===`prompt`&&!G(u).questions?.length&&G(T)!==`now`?{trigger:G(T)===`quota`?`quota`:`at`,zone:Intl.DateTimeFormat().resolvedOptions().timeZone,...G(T)===`quota`?{}:{at:(G(T)===`delay`?new Date(Date.now()+G(E)*6e4):new Date(G(D))).toISOString()}}:void 0;L(ee,!!r),L(te,r?r.trigger===`quota`?`After fresh quota becomes available, when the session is idle`:_e(r.at)+` — not before this time; when quota and idle allow`:``,!0);let i=await ya(`prepare`,{session:t.session,...a()?{review:a()}:{},offer:n,...e?{sendId:e.id}:{},...r?{schedule:r}:{},...r&&G(x)?{editId:G(S)}:{},...G(u).kind===`approval`||G(u).kind===`profile`?{choice:G(f)}:{answers:e?[e.text]:[...G(d)]}},j.signal);G(u)?.id!==n||j.signal.aborted||G(ce)||o()||G(le)||(L(p,i.confirmation,!0),L(m,Date.parse(i.expires),!0))}catch(e){j.signal.aborted||L(_,e instanceof Error?e.message:`Unable to prepare action.`,!0)}finally{L(g,!1)}$.data?.noConfirm&&G(p)&&!j.signal.aborted&&await M()}async function M(){if(!G(u)?.id||G(g)||o()||G(le)||!G(fe))return;let e=G(u).id,n=G(u).kind,r=G(p);L(p,``),L(g,!0),L(b,e,!0);try{await ya(`commit`,{session:t.session,...a()?{review:a()}:{},offer:e,confirmation:r},j.signal),L(v,!0),L(x,!1),G(ee)&&(L(b,``),await ae()),L(_,G(ee)?`Trigger set. Closing Codexometer cancels it.`:n===`profile`?`Profile decision completed.`:n===`approval`?`Decision sent.`:`Text sent.`,!0)}catch(e){L(_,e instanceof _a?e.message:`Outcome uncertain. Check Codex before taking another action; nothing was retried.`,!0)}finally{L(g,!1),L(w,``),await ae(),L(d,[],!0),L(f,null)}}var be=es(),xe=z(be),Ce=e=>{var t=Pr();Z(z(t),17,()=>G(O).filter(e=>e.status!==`sent`),e=>e.id,(e,t)=>{var n=Go(),r=R(n),i=B(r,!0),a=V(r,2),s=B(a),c=V(a,2),l=B(c,!0),d=V(c,2),f=B(d,!0),m=V(d,2),h=B(m,!0),v=V(m,2),y=e=>{var t=Ho(),n=B(t,!0);H(()=>Y(n,G(_))),J(e,t)};X(v,e=>{G(_)&&e(y)});var b=V(v,4),x=V(b,2),S=V(x,2),C=e=>{var n=Uo(),r=V(z(n),2),i=V(r,2);H(()=>r.disabled=G(g)||G(ce)||!G(t).canSend),K(`click`,r,M),K(`click`,i,()=>{L(p,``),L(w,``)}),J(e,n)},T=e=>{var n=Wo();H(()=>n.disabled=G(g)||G(ce)||!G(t).canSend||o()),K(`click`,n,()=>ye(G(t))),J(e,n)};X(S,e=>{G(fe)&&G(w)===G(t).id?e(C):e(T,-1)}),N(n),H((e,n)=>{Y(i,G(t).status===`uncertain`?`CHECK TRIGGER — outcome uncertain; not retried`:`SCHEDULED FOLLOW-UP`),Y(s,`TARGET // ${G(t).session??``}`),Y(l,e),Y(f,n),Y(h,G(t).text),b.disabled=G(g)||!G(u)?.id||G(u).kind!==`prompt`||!!G(u).questions?.length||G(t).status!==`pending`,x.disabled=G(g)||G(t).status===`sending`},[()=>G(t).trigger===`quota`?`After confirmed quota recovery, when idle`:`Not before ${_e(G(t).at)}`,()=>ve(G(t))]),K(`click`,b,()=>ie(G(t))),K(`click`,x,()=>oe(G(t).id)),J(e,n)}),J(e,t)},we=P(()=>!a()&&G(u)?.kind!==`approval`&&!G(u)?.questions?.length&&![`APPROVAL NEEDED`,`INPUT NEEDED`].includes(G(se)||``));X(xe,e=>{G(we)&&e(Ce)});var Te=V(xe,2),Ee=e=>{var r=Cs(),o=R(r),s=B(o,!0),c=V(o,2),S=e=>{var t=Ko(),n=z(t),r=B(n),i=V(n,6),a=B(i,!0),o=B(V(i,6),!0);Se(2),H(()=>{Y(r,`Your ${G(u).profile.threshold??``}% quota threshold has been reached. Review - the profile for subsequent turns.`),Y(a,G(u).profile.current),Y(o,G(u).profile.proposed)}),J(e,t)};X(c,e=>{G(u)?.profile&&!G(ce)&&!G(y)&&e(S)});var C=V(c,2),w=e=>{var t=qo();let n;var r=B(t,!0);H(()=>{n=li(t,1,`svelte-1oupzfc`,null,n,{notice:!G(v),sent:G(v)}),Y(r,G(_))}),J(e,t)};X(C,e=>{G(_)&&e(w)});var O=V(C,2),ne=e=>{Vo(e,{get lines(){return G(u).fileChanges}})},ie=e=>{Vo(e,{get lines(){return i()}})},ae=e=>{var t=Jo(),n=z(t),r=B(n,!0),i=B(V(n,2),!0);H(()=>{Y(r,G(ue)?`EXACT COMMAND`:`LAST OBSERVED COMMAND`),Y(i,G(de))}),J(e,t)},oe=e=>{J(e,Yo())};X(O,e=>{G(u)?.fileChanges?.length&&!G(ce)&&!G(y)?e(ne):i().length?e(ie,1):G(de)?e(ae,2):a()!==`profile`&&G(se)===`APPROVAL NEEDED`&&!G(v)&&e(oe,3)});var le=V(O,2),me=e=>{J(e,Xo())},ve=e=>{J(e,Zo())},j=e=>{var t=es(),n=z(t),r=e=>{var t=Qo(),n=B(t,!0);H(e=>Y(n,e),[()=>G(se)===`WORKING`?`Codex is working — nothing to respond to.`:[`APPROVAL NEEDED`,`INPUT NEEDED`].includes(G(se)||``)?`Respond in Codex for this request.`:`Nothing needs a response right now.`]),J(e,t)};X(n,e=>{(G(se)===`WORKING`||!G(v)&&!G(g))&&e(r)});var i=V(n,2),a=e=>{J(e,$o())},o=P(()=>[`APPROVAL NEEDED`,`INPUT NEEDED`].includes(G(se)||``)&&!G(v));X(i,e=>{G(o)&&e(a)}),J(e,t)},be=e=>{var r=Ss(),i=z(r),o=B(i),s=V(i,2),c=R(s),_=B(c,!0),v=V(c,2),y=e=>{var r=Pr();Z(z(r),17,()=>G(u).choices||[],qr,(e,r,i)=>{var o=rs(),s=R(o);Si(s),s.value=s.__value=i;var c=V(s),l=V(c),u=e=>{var t=ts(),n=B(t,!0);H(()=>Y(n,G(r).detail)),J(e,t)};X(l,e=>{G(r).detail&&e(u)});var d=V(l,2),p=e=>{J(e,ns())};X(d,e=>{G(r).persistent&&e(p)}),N(o),H(()=>{Q(s,`name`,`decision-`+t.session+`-`+a()),Y(c,` ${G(r).label??``} `)}),ki(n,[],s,()=>G(f),e=>L(f,e)),J(e,o)}),J(e,r)},b=e=>{var n=es(),r=z(n),i=e=>{var t=us(),n=z(t),r=V(R(n)),i=R(r),a=e=>{var t=is();t.value=t.__value=`now`,J(e,t)};X(i,e=>{G(x)||e(a)});var o=V(i);o.value=o.__value=`quota`;var s=V(o);s.value=s.__value=`delay`;var c=V(s);c.value=c.__value=`at`,N(r),hi(r),N(n);var l=V(n,2),u=e=>{var t=as(),n=V(R(t));Si(n),N(t),Di(n,()=>G(E),e=>L(E,e)),J(e,t)};X(l,e=>{G(T)===`delay`&&e(u)});var d=V(l,2),f=e=>{var t=os(),n=V(R(t));Si(n),N(t),Di(n,()=>G(D),e=>L(D,e)),J(e,t)};X(d,e=>{G(T)===`at`&&e(f)});var p=V(d,2),m=e=>{var t=cs(),n=z(t),r=B(n),i=V(n,2),a=e=>{J(e,ss())};X(i,e=>{G(T)!==`quota`&&e(a)});var o=V(i,2);o.textContent=`Timezone: ${Intl.DateTimeFormat().resolvedOptions().timeZone??``}. - One pending request per session; saving replaces it. Memory - only; Codexometer must stay running. Uses session model, - reasoning and speed.`,H(e=>Y(r,`Will send: ${e??``}`),[()=>G(T)===`quota`?`when quota is available and this session is idle`:Number.isFinite(G(he))?_e(G(he)):`choose a date and time`]),J(e,t)};X(p,e=>{G(T)!==`now`&&e(m)});var h=V(p,2),g=e=>{J(e,ls())};X(h,e=>{G(x)&&e(g)}),gi(r,()=>G(T),e=>L(T,e)),J(e,t)};X(r,e=>{G(u).questions?.length||e(i)}),Z(V(r,2),17,()=>G(pe),qr,(e,n,r)=>{var i=_s(),o=z(i),s=e=>{{let n=P(()=>G(d)[0]||``);Li(Lo(e,{get session(){return t.session},get oncopy(){return l()},get query(){return G(n)},oncomplete:e=>{L(d,[e],!0),ur().then(()=>G(k)?.focus())},onchange:()=>{L(d,[``],!0),ur().then(()=>G(k)?.focus())}}),e=>L(re,e,!0),()=>G(re))}};X(o,e=>{r===0&&a()!==`profile`&&G(u).kind===`prompt`&&!G(u).questions?.length&&e(s)});var c=V(o,2),f=R(c),p=V(f),m=e=>{var t=ds();Si(t),Di(t,()=>G(d)[r],e=>G(d)[r]=e),J(e,t)},h=e=>{var t=ps(),i=R(t);i.value=i.__value=``,Z(V(i),17,()=>G(n).options||[],qr,(e,t)=>{var n=fs(),r=B(n,!0),i={};H(()=>{Y(r,G(t)),i!==(i=G(t))&&(n.value=(n.__value=i)??``)}),J(e,n)}),N(t),hi(t),gi(t,()=>G(d)[r],e=>G(d)[r]=e),J(e,t)},g=e=>{var t=ms(),i=z(t);nt(i),Li(i,e=>L(k,e),()=>G(k));var a=V(i,2),o=e=>{var t=Qo(),r=B(t);H(e=>Y(r,`Suggested answers: ${e??``}`),[()=>G(n).options.join(` · `)]),J(e,t)};X(a,e=>{G(n).options?.length&&e(o)}),K(`keydown`,i,e=>{G(u)?.kind===`prompt`&&!G(u).questions?.length&&G(re)?.handleKey(e)}),Di(i,()=>G(d)[r],e=>G(d)[r]=e),J(e,t)};X(p,e=>{G(n).secret?e(m):G(n).freeText?e(g,-1):e(h,1)}),N(c);var _=V(c,2),v=e=>{var t=gs(),r=V(R(t),4);Z(r,21,()=>G(n).options||[],qr,(e,t)=>{var n=hs(),r=B(n,!0);H(()=>Y(r,G(t))),J(e,n)}),N(r),N(t),J(e,t)};X(_,e=>{G(n).secret&&!G(n).freeText&&e(v)}),H(()=>Y(f,`${G(n).text??``} `)),J(e,i)}),J(e,n)};X(v,e=>{G(u).kind===`approval`||G(u).kind===`profile`?e(y):e(b,-1)}),N(s);var S=V(s,2),C=e=>{var t=ys(),n=z(t),r=e=>{var t=vs(),n=B(t);H(()=>Y(n,`TRIGGER // ${G(te)??``}`)),J(e,t)};X(n,e=>{G(ee)&&e(r)});var i=V(n,2),a=B(i),o=V(i,2),s=B(o),c=V(o,2);H(e=>{Y(a,`Check the target and ${G(u).kind===`profile`?`profile above`:G(u).kind===`approval`?G(u).fileChanges?.length?`proposed file changes above`:`exact command and permission scope`:`message above`}. ${G(ee)?`This schedules a follow-up that will send automatically when eligible`:`This will send to Codex`}; it may start work using your quota. - Confirmation expires in ${e??``}s.`),o.disabled=G(g)||G(ce),Y(s,`CONFIRM ${(G(u).kind===`approval`||G(u).kind===`profile`?G(u).choices?.[G(f)]?.label:G(ee)?G(x)?`SAVE CHANGES`:`SCHEDULE`:`SEND`)??``}`)},[()=>Math.max(0,Math.ceil((G(m)-G(h))/1e3))]),K(`click`,o,M),K(`click`,c,()=>{L(p,``)}),J(e,t)},w=e=>{var t=bs(),n=z(t),r=e=>{var t=Ho(),n=B(t,!0);H(()=>Y(n,G(ge))),J(e,t)};X(n,e=>{G(ge)&&e(r)});var i=V(n,2),a=B(i,!0);H(()=>{i.disabled=G(g)||G(ce)||!G(A)||!!G(ge),Y(a,G(g)?`SENDING…`:$.data?.noConfirm?G(x)?`SAVE CHANGES`:G(u).kind===`approval`||G(u).kind===`profile`?`SEND DECISION`:G(u).kind===`prompt`&&!G(u).questions?.length&&G(T)!==`now`?`SET TRIGGER`:`SEND`:G(x)?`REVIEW CHANGES`:`REVIEW BEFORE SENDING`)}),K(`click`,i,()=>ye()),J(e,t)};X(S,e=>{G(fe)&&!$.data?.noConfirm?e(C):e(w,-1)});var O=V(S,2),ne=e=>{var t=xs();K(`click`,t,()=>{L(x,!1),L(p,``),L(d,[],!0)}),J(e,t)};X(O,e=>{G(x)&&e(ne)}),H(()=>{Y(o,`TARGET // ${G(u).thread??``} // ${(G(u).directory||`Directory unavailable`)??``}`),s.disabled=G(g)||G(fe)||G(ce),Y(_,G(u).kind===`approval`||G(u).kind===`profile`?`Choose a decision`:G(x)?`EDIT SCHEDULED FOLLOW-UP`:`Reply to this session`)}),J(e,r)};X(le,e=>{G(ce)||G(y)?e(me):G(u)?G(u).id?G(b)!==G(u).id&&e(be,3):e(j,2):e(ve,1)}),N(r),H(()=>Y(s,a()===`profile`?`QUOTA THRESHOLD`:`SESSION CONTROL // EXPERIMENTAL`)),J(e,r)};X(Te,e=>{!G(le)&&(!G(ne)||G(x)||a()||G(u)?.kind!==`prompt`||G(u).questions?.length)&&e(Ee)}),J(e,be),Ge()}Cr([`click`,`keydown`]);var Ts=q(` `);function Es(e,t){We(t,!0);let n=Gi(t,`stale`,3,!1),r=P(()=>n()||!!t.goal?.stale);var i=Pr(),a=z(i),o=e=>{var n=Ts();let i;var a=B(n,!0);H((e,o)=>{i=li(n,1,`goal-status svelte-1yfu3xi`,null,i,{stale:G(r),pursuing:!G(r)&&t.goal.status===`active`,blocked:e}),Q(n,`title`,o),Y(a,G(r)?`GOAL STALE`:t.goal.label)},[()=>!G(r)&&[`blocked`,`usageLimited`,`budgetLimited`].includes(t.goal.status),()=>t.goal.objective+` -`+t.goal.tokensUsed.toLocaleString()+(t.goal.tokenBudget===null?` tokens`:` / `+t.goal.tokenBudget.toLocaleString()+` tokens`)]),J(e,n)};X(a,e=>{t.goal&&e(o)}),J(e,i),Ge()}var Ds=q(` `,1),Os=q(`
        `),ks=q(`

        STATUS LINE // CODEXOMETER

        Choose multiple fields and their order. These display preferences do not - change Codex CLI configuration. Missing values are omitted.

        `),As=q(`

        `,1);function js(e,t){We(t,!0);let n=Gi(t,`stale`,3,!1),r=I(Xt([])),i=I(!1),a=P(()=>$.data?.statusLineFields||[]),o=e=>[...new Set(e??G(a).filter(e=>e.default).map(e=>e.id))].filter(e=>G(a).some(t=>t.id===e)),s=e=>o(e).map(e=>e===`run-state`&&(n()||!$.connected||$.data?.sessionsError)?`STALE`:t.session.statusLine?.[e]).filter(Boolean).join(` · `),c=P(()=>[...G(r).map(e=>G(a).find(t=>t.id===e)).filter(Boolean),...G(a).filter(e=>!G(r).includes(e.id))]);yn(()=>{sa.open&&!G(i)?(L(r,o(oa.statusLine),!0),L(i,!0)):sa.open||L(i,!1)});function l(e,t){L(r,t?[...G(r),e]:G(r).filter(t=>t!==e),!0)}function u(e,t){let n=G(r).indexOf(e),i=n+t;if(n<0||i<0||i>=G(r).length)return;let a=[...G(r)];[a[n],a[i]]=[a[i],a[n]],L(r,a,!0)}var d=As(),p=z(d),m=e=>{var t=ks(),n=V(R(t),4);Z(n,21,()=>G(c),e=>e.id,(e,t)=>{var n=Os(),i=R(n),a=R(i);Si(a);var o=V(a,1,!0),s=B(V(o),!0);N(i);var c=V(i,2),d=e=>{var n=Ds(),i=z(n),a=V(i,2);H((e,n)=>{Q(i,`aria-label`,`Move `+G(t).label+` earlier`),i.disabled=e,Q(a,`aria-label`,`Move `+G(t).label+` later`),a.disabled=n},[()=>G(r).indexOf(G(t).id)===0,()=>G(r).indexOf(G(t).id)===G(r).length-1]),K(`click`,i,()=>u(G(t).id,-1)),K(`click`,a,()=>u(G(t).id,1)),J(e,n)},f=P(()=>G(r).includes(G(t).id));X(c,e=>{G(f)&&e(d)}),N(n),H(e=>{Q(i,`title`,G(t).help),Ci(a,e),Y(o,G(t).label),Y(s,G(t).help)},[()=>G(r).includes(G(t).id)]),K(`change`,a,e=>l(G(t).id,e.currentTarget.checked)),J(e,n)}),N(n);var i=V(n,2),a=B(i),o=V(i,2),d=V(o,2);N(t),H(e=>Y(a,`Preview // ${e??``}`),[()=>s(G(r))||`—`]),K(`click`,o,()=>{oa.statusLine=[...G(r)],la(),sa.open=!1}),K(`click`,d,()=>{sa.open=!1}),J(e,t)};X(p,e=>{sa.open&&e(m)});var h=V(p,2),g=R(h),_=B(g,!0),v=V(g,2);{let e=P(()=>n()||!$.connected||!!$.data?.sessionsError);Es(v,{get goal(){return t.session.goal},get stale(){return G(e)}})}var y=V(v,2);ni(V(y,2),()=>t.children??f),N(h),H((e,t)=>{Q(g,`title`,e),Y(_,t)},[()=>s(oa.statusLine),()=>s(oa.statusLine)]),K(`click`,y,()=>{sa.open=!sa.open}),J(e,d),Ge()}Cr([`change`,`click`]);var Ms=q(`
        `);function Ns(e,t){We(t,!0);let n=Gi(t,`active`,3,!1),r=Gi(t,`compact`,3,!1),i=P(()=>oo(t.session)),a=P(()=>!!G(i)),o=I(!1),s=I(``),c=I(!1);yn(()=>{if(!G(c))return;let e=setTimeout(()=>{L(c,!1)},150);return()=>clearTimeout(e)}),yn(()=>{G(i),t.session.status,L(s,``)});async function l(){if(!G(a)||G(o))return;let e=G(i);L(o,!0),L(c,!0),L(s,``);try{await navigator.clipboard.writeText(e),G(i)===e&&L(s,`Copied.`)}catch{G(i)===e&&L(s,`Clipboard unavailable. Select the visible text and copy it manually.`)}finally{L(o,!1)}}function u(e){!n()||!G(a)||e.defaultPrevented||e.repeat||e.ctrlKey||e.metaKey||e.altKey||e.key.toLowerCase()!==`c`||e.target instanceof Element&&e.target.closest(`input, textarea, select, [contenteditable]`)||(e.preventDefault(),l())}var d={copy:l},f=Pr();Sr(`keydown`,$t,u);var p=z(f),m=e=>{var t=Ms();let n;var i=R(t),a=B(i,!0),u=V(i,2);let d;N(t),H(()=>{n=li(t,1,`session-copy svelte-543j00`,null,n,{compact:r()}),Y(a,G(s)),u.disabled=G(o),d=li(u,1,`svelte-543j00`,null,d,{flashed:G(c)})}),K(`click`,u,l),J(e,t)};return X(p,e=>{G(a)&&e(m)}),J(e,f),Ge(d)}Cr([`click`]);var Ps=q(`

         
        `,1),Fs=q(`

        LATEST GUIDANCE

         
        `,1),Is=q(`

        CONTEXT

         

        `,1),Ls=q(`

        `),Rs=q(`

        `),zs=q(`
         
        `),Bs=q(`

         
        `,1),Vs=q(`
         
        `),Hs=q(`

        Command unavailable from this observation. Open Codex to inspect the - request.

        `),Us=q(`

        `,1),Ws=q(`

        `),Gs=q(` `,1),Ks=q(`
        `),qs=q(`

        Session connection or refresh unavailable. Context and telemetry may be - stale.

        `),Js=q(`

        Some quota profile checks are unavailable. Only sessions with freshly - verified quota and settings can be updated; previous outcome notices remain - visible.

        `),Ys=q(` `),Xs=q(` `),Zs=q(` `),Qs=q(``),$s=q(`

        Read only — reply or approve in Codex.

        `),ec=q(`

        `),tc=q(`QUOTA THRESHOLD // REVIEW PROFILE ↗`),nc=q(`
        `),rc=q(` `,1),ic=q(`
        `),ac=q(`

        This session is no longer in the current observation. Return to sessions.

        `),oc=q(`

        `),sc=q(``),cc=q(`

        `),lc=q(` `),uc=q(`

        `),dc=q(`QUOTA THRESHOLD // REVIEW PROFILE ↗`),fc=q(`

        `),pc=q(`

        TOKEN ACTIVITY // 30 SECOND SAMPLES

        `),mc=q(`

        TOKENS

        FULL DETAIL →
        `),hc=q(`

        No locally observed sessions yet. Keep Codex running alongside - Codexometer.

        `),gc=q(`

        ↑ ↓ SELECT SESSION // ← LESS DETAIL // → MORE DETAIL // ESC BACK

        `,1),_c=q(`

        SESSION TOTALS

        `,1);function vc(e,t){We(t,!0);let n=(e,t=f,n,r)=>{let i=mt(()=>g(n?.(),!0)),a=mt(()=>g(r?.(),!1));var o=Gs(),s=z(o),c=e=>{var n=Ps(),r=z(n),i=B(r,!0),a=B(V(r,2),!0);H(()=>{Y(i,t().contextKind===`LAST REPLY`&&!t().streaming?`TASK`:`CURRENT TASK`),Y(a,t().currentTask)}),J(e,n)};X(s,e=>{t().currentTask&&e(c)});var l=V(s,2),u=e=>{var n=Fs(),r=B(V(z(n),2),!0);H(()=>Y(r,t().latestGuidance)),J(e,n)};X(l,e=>{t().latestGuidance&&e(u)});var d=V(l,2),p=e=>{var n=Is(),r=B(V(z(n),2),!0);Se(2),H(()=>Y(r,t().approvalContext)),J(e,n)};X(d,e=>{G(a)&&t().contextKind===`APPROVAL REQUEST`&&t().approvalContext&&e(p)});var m=V(d,2),h=e=>{var n=Ls();let r;var i=B(n,!0);H(e=>{r=li(n,1,`attention-note`,null,r,{inferred:t().status===`CHECK SESSION`}),Y(i,e)},[()=>D(t())]),J(e,n)},_=P(()=>D(t())&&(!G(a)||G(v)||t().status===`CHECK SESSION`||y(t())));X(m,e=>{G(_)&&e(h)});var b=V(m,2),x=e=>{var n=Rs(),r=B(n,!0);H(e=>Y(r,e),[()=>ee(t())]),J(e,n)};X(b,e=>{G(i)&&e(x)});var S=V(b,2),C=e=>{var n=zs(),r=B(n,!0);H(()=>Y(r,t().text||`No session context available.`)),J(e,n)};X(S,e=>{(t().text||!t().workingCommand)&&e(C)});var w=V(S,2),T=e=>{var n=Bs(),r=V(z(n),2),i=R(r),a=V(i),o=e=>{var n=Nr();H(()=>Y(n,`// ${t().commandTiming??``}`)),J(e,n)};X(a,e=>{t().commandTiming&&e(o)});var s=V(a,2),c=e=>{var n=Nr();H(()=>Y(n,`// +${(t().runningCommands||0)-+(t().commandStatus===`running`)} RUNNING`)),J(e,n)};X(s,e=>{!G(v)&&(t().runningCommands||0)>+(t().commandStatus===`running`)&&e(c)});var l=V(s,2),u=e=>{J(e,Nr(`+`))};X(l,e=>{t().runningLimited&&e(u)}),N(r);var d=B(V(r,2),!0);H(e=>{Y(i,`COMMAND // ${e??``} `),Y(d,t().workingCommand)},[()=>G(v)?`UNKNOWN`:(t().commandStatus||`unknown`).toUpperCase()]),J(e,n)};X(w,e=>{t().workingCommand&&e(T)});var E=V(w,2),te=e=>{Vo(e,{get lines(){return t().fileChanges}})},O=e=>{var n=Us(),r=V(z(n),2),i=B(r,!0),a=V(r,2),o=e=>{var n=Vs(),r=B(n,!0);H(()=>Y(r,t().command)),J(e,n)},s=e=>{J(e,Hs())};X(a,e=>{t().command?e(o):e(s,-1)}),H(()=>Y(i,G(v)||t().status!==`APPROVAL NEEDED`?`LAST OBSERVED COMMAND`:$.data?.control?`COMMAND REQUEST`:`COMMAND TO APPROVE IN CODEX`)),J(e,n)};X(E,e=>{(!G(a)||!$.data?.control)&&t().fileChanges?.length?e(te):G(a)&&$.data?.control||!t().command&&t().status!==`APPROVAL NEEDED`||e(O,1)});var ne=V(E,2),k=e=>{var n=Ws(),r=B(n);H(()=>Y(r,`CONTEXT SOURCE // ${(t().source||`LOCAL`)??``} // OBSERVATION`)),J(e,n)};X(ne,e=>{G(a)||e(k)}),J(e,o)},r=I(void 0),i=Gi(t,`params`,19,()=>({})),a=P(()=>$.data?.sessions||[]),o=P(()=>$.data?.control&&$.data.profiles||[]),s=P(()=>($.data?.triggers||[]).filter(e=>G(a).some(t=>t.id===e.session)));function c(e,t){i().id&&i().id!==t&&G(u)?e.preventDefault():w(t)}function l(e){return[`TURN COMPLETE`,`IDLE`,`ACTIVE`].includes(e.status)&&G(s).some(t=>t.session===e.id)?`TRIGGER SET`:e.status}let u=I(!1);function d(e,t){i().id&&i().id!==t&&G(u)?e.preventDefault():w(t)}yn(()=>{let e=i().id;e&&pr(()=>{oa.selected=e,pa(e,2)})});let p=P(()=>G(a).find(e=>e.id===i().id)),m=P(()=>new URLSearchParams(Xi.querystring).get(`review`)===`profile`&&G(o).some(e=>e.session===i().id&&e.pending)),h=P(()=>G(a).some(e=>e.id===oa.selected)?oa.selected:G(a)[0]?.id),v=P(()=>!$.connected||!!$.data?.sessionsError);function y(e){return e.status!==`STALE`&&e.failed===!0}let b=P(()=>[[`OBSERVED TOKENS`,ma(G(a).reduce((e,t)=>e+t.tokens,0))],[`LISTED SESSIONS`,ma(G(a).length)],...[[`WORKING`,`WORKING`],[`AWAITING APPROVAL`,`APPROVAL NEEDED`],[`AWAITING INPUT`,`INPUT NEEDED`],[`ERRORS`,`ERRORS`],[`CHECK · INFERRED`,`CHECK SESSION`]].map(([e,t])=>[e,G(v)?`—`:ma(G(a).filter(e=>t===`ERRORS`?y(e):e.status===t).length)])]),x=P(()=>G(a).filter(e=>[`INPUT NEEDED`,`APPROVAL NEEDED`].includes(e.status)||y(e)).sort((e,t)=>S(e)-S(t)));function S(e){return e.status===`APPROVAL NEEDED`?0:e.status===`INPUT NEEDED`?1:2}let C=P(()=>G(a).filter(e=>e.status===`TURN COMPLETE`&&!G(s).some(t=>t.session===e.id)&&!G(o).some(t=>t.session===e.id&&t.pending)));function w(e){oa.selected=e,ur().then(()=>document.querySelector(`.session-row.selected`)?.scrollIntoView({block:`nearest`}))}function T(e,t){if(w(e),i().id){t<0&&(pa(e,2),location.hash=`/sessions`);return}let n=da(e)+t;n>2?location.hash=`/sessions/`+encodeURIComponent(e):pa(e,n)}function E(e){let t=e.target;if(!(e.altKey||e.ctrlKey||e.metaKey||e.shiftKey||t.closest(`input, textarea, select, [contenteditable="true"]`))){if(e.key===`Escape`&&i().id)e.preventDefault(),T(i().id,-1);else if([`ArrowLeft`,`ArrowRight`].includes(e.key)&&(i().id||G(h)))e.preventDefault(),T(i().id||G(h),e.key===`ArrowLeft`?-1:1);else if(!i().id&&[`ArrowUp`,`ArrowDown`].includes(e.key)&&G(a).length){e.preventDefault();let t=G(a).findIndex(e=>e.id===G(h));w(G(a)[Math.max(0,Math.min(G(a).length-1,t+(e.key===`ArrowUp`?-1:1)))].id)}}}function D(e){return G(v)?`Last observation only — refresh unavailable. Check Codex for current state.`:e.status===`CHECK SESSION`?`INFERRED INACTIVITY — a quiet session, not a confirmed input or approval request. Local tools may still be running.`:y(e)?e.status===`MODEL BUSY`?`CONFIRMED TURN FAILURE — the selected model was at capacity. Choose another model with /model or retry when ready. This is not a quota-exhaustion signal.`:e.status===`MODEL UNAVAILABLE`?`CONFIRMED TURN FAILURE — the provider could not find the selected model. Choose an available model with /model. No automatic retry or model change.`:e.status===`CAPACITY RESTRICTED`?`CONFIRMED TURN FAILURE — capacity is unavailable on a policy-restricted route. Inspect the error before changing settings or retrying. This is not a quota-exhaustion signal; no automatic retry or policy change.`:`CONFIRMED TURN FAILURE — inspect the error below before sending another prompt. No automatic retry or model change.`:$.data?.control&&[`APPROVAL NEEDED`,`INPUT NEEDED`].includes(e.status)?`Open full detail for supported live controls; otherwise reply in Codex.`:e.status===`APPROVAL NEEDED`?`OBSERVED APPROVAL SIGNAL — approve or decline in Codex.`:e.status===`INPUT NEEDED`?`OBSERVED INPUT SIGNAL — reply in Codex.`:e.status===`TURN COMPLETE`?`OBSERVED TURN COMPLETION — informational, not an approval request.`:``}function ee(e){return e.streaming&&e.contextKind===`LAST REPLY`?`REPLY // STREAMING`:e.contextKind||`LAST ACTIVITY`}var te=_c();Sr(`keydown`,$t,E);var O=z(te),ne=B(V(R(O),2));N(O);var k=V(O,2);let re;Z(k,21,()=>G(b),qr,(e,t)=>{var n=P(()=>_(G(t),2));let r=()=>G(n)[0],i=()=>G(n)[1];var a=Ks();let o;var s=R(a),c=B(s,!0),l=B(V(s,2),!0);N(a),H(()=>{o=li(a,1,`svelte-mm4wfe`,null,o,{failed:r()===`ERRORS`&&i()!==`0`&&i()!==`—`}),Y(c,r()),Y(l,i())}),J(e,a)}),N(k);var ie=V(k,2),ae=B(ie),oe=V(ie,2),se=e=>{J(e,qs())};X(oe,e=>{G(v)&&e(se)});var ce=V(oe,2),le=e=>{J(e,Js())};X(ce,e=>{$.data?.profileError&&e(le)});var ue=V(ce,2),de=e=>{var t=Qs(),n=R(t);Z(n,17,()=>G(x),qr,(e,t)=>{var n=Ys();let r;var i=B(n);H((e,a,o)=>{r=li(n,1,`button`,null,r,{approval:e}),Q(n,`href`,a),Y(i,`${G(t).status??``} - ${o??``} // ${(G(t).name||G(t).directory||G(t).id)??``}`)},[()=>G(t).status===`APPROVAL NEEDED`||G(t).status===`INPUT NEEDED`||y(G(t)),()=>`#/sessions/`+encodeURIComponent(G(t).id),()=>Array.from(G(t).id).slice(-5).join(``).toUpperCase()]),K(`click`,n,()=>w(G(t).id)),J(e,n)});var r=V(n,2);Z(r,17,()=>G(o).filter(e=>e.pending&&G(a).some(t=>t.id===e.session)),qr,(e,t)=>{var n=Xs(),r=B(n);H((e,a,o)=>{Q(n,`title`,i().id&&i().id!==G(t).session&&G(u)?`Finish sending or clear your current draft before switching sessions.`:`Review this session’s quota threshold`),Q(n,`href`,e),Y(r,`QUOTA THRESHOLD ${a??``} // ${o??``}`)},[()=>`#/sessions/`+encodeURIComponent(G(t).session)+`?review=profile`,()=>Array.from(G(t).session).slice(-5).join(``).toUpperCase(),()=>G(a).find(e=>e.id===G(t).session)?.name||G(a).find(e=>e.id===G(t).session)?.directory||G(t).session]),K(`click`,n,e=>d(e,G(t).session)),J(e,n)});var l=V(r,2);Z(l,17,()=>G(C),qr,(e,t)=>{var n=Zs(),r=B(n);H((e,i)=>{Q(n,`href`,e),Y(r,`TURN COMPLETE ${i??``} // - ${(G(t).name||G(t).directory||G(t).id)??``}`)},[()=>`#/sessions/`+encodeURIComponent(G(t).id),()=>Array.from(G(t).id).slice(-5).join(``).toUpperCase()]),K(`click`,n,()=>w(G(t).id)),J(e,n)}),Z(V(l,2),17,()=>G(s),qr,(e,t)=>{var n=Zs(),r=B(n);H((e,t,i)=>{Q(n,`href`,e),Y(r,`TRIGGER SET ${t??``} // ${i??``}`)},[()=>`#/sessions/`+encodeURIComponent(G(t).session),()=>Array.from(G(t).session).slice(-5).join(``).toUpperCase(),()=>G(a).find(e=>e.id===G(t).session)?.name||G(a).find(e=>e.id===G(t).session)?.directory]),K(`click`,n,e=>c(e,G(t).session)),J(e,n)}),N(t),J(e,t)},fe=P(()=>(G(x).length||G(C).length||G(o).some(e=>e.pending)||G(s).length)&&!G(v));X(ue,e=>{G(fe)&&e(de)});var pe=V(ue,2),A=e=>{var t=Pr(),a=z(t),s=e=>{var t=ic(),a=R(t),s=R(a);let c;var d=R(s);let f;var h=V(d);N(s);var g=V(s,2);N(a);var _=V(a,2),b=e=>{var t=Ws(),n=B(t,!0);H(()=>Y(n,G(p).directory)),J(e,t)};X(_,e=>{G(p).name&&G(p).directory&&e(b)});var x=V(_,2),S=B(x),C=V(x,2);let T;var E=R(C),D=R(E);n(D,()=>G(p),()=>!0,()=>!0),N(E);var ee=V(E,2),te=e=>{var t=Pr();Kr(z(t),()=>G(p).id,e=>{{let t=P(()=>new URLSearchParams(Xi.querystring).get(`schedule`)===`edit`),n=P(()=>G(p).fileChanges||[]);ws(e,{get session(){return G(p).id},oncopy:()=>G(r)?.copy(),get openScheduler(){return G(t)},get observedCommand(){return G(p).command},get observedFileChanges(){return G(n)},get suspended(){return G(m)},onProtectedChange:e=>{L(u,e,!0)}})}}),J(e,t)},O=e=>{var t=$s();Kr(V(R(t),2),()=>G(p).id,e=>{Lo(e,{get session(){return G(p).id},localOnly:!0,oncopy:()=>G(r)?.copy()})}),N(t),J(e,t)};X(ee,e=>{$.data?.control?e(te):e(O,-1)}),N(C);var ne=V(C,2);Z(ne,17,()=>G(o).filter(e=>e.session===G(p).id),e=>e.session,(e,t)=>{var n=nc(),r=R(n),i=e=>{var n=ec(),r=B(n,!0);H(()=>Y(r,G(t).notice)),J(e,n)};X(r,e=>{G(t).notice&&e(i)});var a=V(r,2),o=e=>{ws(e,{get session(){return G(p).id},review:`profile`})},s=e=>{var t=tc();H(e=>Q(t,`href`,e),[()=>`#/sessions/`+encodeURIComponent(G(p).id)+`?review=profile`]),J(e,t)};X(a,e=>{G(t).pending&&G(m)?e(o):G(t).pending&&e(s,1)}),N(n),H(()=>Q(n,`id`,`quota-profile-`+G(p).id)),J(e,n)});var k=V(ne,2),re=e=>{var t=Pr();Kr(z(t),()=>G(p).id,e=>{js(e,{get session(){return G(p)},get stale(){return G(v)},children:e=>{var t=Pr(),n=z(t),i=e=>{Li(Ns(e,{get session(){return G(p)},active:!0,compact:!0}),e=>L(r,e,!0),()=>G(r))};X(n,e=>{G(m)||e(i)}),J(e,t)},$$slots:{default:!0}})}),J(e,t)},ie=e=>{var t=rc(),n=z(t),i=e=>{var t=Pr();Kr(z(t),()=>G(p).id,e=>{Li(Ns(e,{get session(){return G(p)},active:!0}),e=>L(r,e,!0),()=>G(r))}),J(e,t)};X(n,e=>{G(m)||e(i)}),Kr(V(n,2),()=>G(p).id,e=>{js(e,{get session(){return G(p)},get stale(){return G(v)}})}),J(e,t)};X(k,e=>{G(p).goal?e(re):e(ie,-1)}),N(t),H((e,t,n,r)=>{c=li(s,1,`svelte-mm4wfe`,null,c,{failed:e}),f=li(d,1,`lamp lit svelte-mm4wfe`,null,f,{failed:t,working:G(p).status===`WORKING`&&!G(v)}),Y(h,`${n??``} // ${(G(p).name||G(p).directory)??``}`),Y(S,`${r??``} TOKENS // ${G(p).id??``} // CONTEXT SOURCE // ${(G(p).source||`LOCAL`)??``}`),T=di(C,``,T,{display:G(m)?`none`:void 0})},[()=>y(G(p))&&!G(v)&&!G(m),()=>y(G(p))&&!G(v),()=>G(v)?`STALE`:G(m)?`QUOTA THRESHOLD`:l(G(p)),()=>ma(G(p).tokens)]),K(`click`,g,()=>{w(i().id),pa(i().id,2)}),J(e,t)},c=e=>{J(e,ac())};X(a,e=>{G(p)?e(s):e(c,-1)}),J(e,t)},me=e=>{var t=gc(),r=z(t),i=V(R(r),2),s=R(i),c=V(s,2);N(i),N(r);var u=V(r,2);Z(u,17,()=>G(a),e=>e.id,(e,t)=>{let r=P(()=>da(G(t).id));var i=mc();let a;var s=R(i),c=R(s),u=e=>{var n=oc(),r=R(n),i=B(r);N(n),H(e=>{Q(r,`aria-pressed`,G(h)===G(t).id),Y(i,`${e??``} // ${G(t).name??``}`)},[()=>Array.from(G(t).id).slice(-5).join(``).toUpperCase()]),K(`click`,r,()=>w(G(t).id)),J(e,n)};X(c,e=>{G(t).name&&e(u)});var f=V(c,2),p=R(f);let m;var g=R(p);let _;var b=V(g,1,!0);N(p),N(f);var x=V(f,2),S=e=>{var n=sc(),r=B(n,!0);H(()=>{Q(n,`aria-pressed`,G(h)===G(t).id),Y(r,G(t).directory||G(t).id)}),K(`click`,n,()=>w(G(t).id)),J(e,n)};X(x,e=>{G(t).name||e(S)});var C=V(x,2),E=R(C);Se(),N(C);var D=V(C,2),te=e=>{var n=Ws(),r=B(n,!0);H(()=>Y(r,G(t).directory)),J(e,n)};X(D,e=>{G(t).name&&G(t).directory&&e(te)});var O=V(D,2),ne=B(O),k=V(O,2),re=B(k),ie=V(k,2);Es(ie,{get goal(){return G(t).goal},get stale(){return G(v)}});var ae=V(ie,2),oe=R(ae),se=V(oe,2),ce=B(se,!0),le=V(se,2);N(ae);var ue=V(ae,2),de=V(ue,2),fe=e=>{var n=cc(),r=B(n,!0);H(e=>Y(r,e),[()=>G(t).status===`CHECK SESSION`?`INFERRED INACTIVITY`:y(G(t))?`OPEN DETAIL FOR THE TURN ERROR`:$.data?.control?`OPEN FULL DETAIL OR REPLY IN CODEX`:G(t).status===`APPROVAL NEEDED`?`APPROVE OR DECLINE IN CODEX`:`REPLY IN CODEX`]),J(e,n)},pe=P(()=>!G(v)&&([`APPROVAL NEEDED`,`INPUT NEEDED`,`CHECK SESSION`].includes(G(t).status)||y(G(t))));X(de,e=>{G(pe)&&e(fe)}),N(s);var A=V(s,2),me=e=>{var r=fc(),i=R(r),a=R(i),s=B(a,!0),c=V(a,2),l=e=>{var n=lc(),r=B(n,!0);H(e=>{Q(n,`href`,e),Y(r,$.data?.control?`REVIEW IN FULL DETAIL ↗`:`APPROVAL IN CODEX ↗`)},[()=>`#/sessions/`+encodeURIComponent(G(t).id)]),J(e,n)};X(c,e=>{!G(v)&&G(t).status===`APPROVAL NEEDED`&&e(l)}),N(i);var u=V(i,2);n(u,()=>G(t),()=>!1);var f=V(u,2);Z(f,17,()=>G(o).filter(e=>e.session===G(t).id),qr,(e,n)=>{var r=rc(),i=z(r),a=e=>{var t=uc(),r=B(t,!0);H(()=>Y(r,G(n).notice)),J(e,t)};X(i,e=>{G(n).notice&&e(a)});var o=V(i,2),s=e=>{var n=dc();H(e=>Q(n,`href`,e),[()=>`#/sessions/`+encodeURIComponent(G(t).id)+`?review=profile`]),K(`click`,n,e=>d(e,G(t).id)),J(e,n)};X(o,e=>{G(n).pending&&e(s)}),J(e,r)});var p=V(f,2);{let e=P(()=>G(h)===G(t).id);Ns(p,{get session(){return G(t)},get active(){return G(e)}})}N(r),H(e=>Y(s,e),[()=>ee(G(t))]),J(e,r)};X(A,e=>{G(r)>0&&e(me)});var he=V(A,2),ge=e=>{var n=pc(),r=V(R(n),2);{let e=P(()=>(G(t).samples||[]).map(e=>e.tokens));ao(r,{get values(){return G(e)},capacity:120})}var i=B(V(r,2));N(n),H(()=>Y(i,`LAST ${(G(t).samples?.length||0)??``} SAMPLES // AUTO SCALE`)),J(e,n)};X(he,e=>{G(r)<2&&e(ge)}),N(i),H((e,n,o,s,c,l,u)=>{a=li(i,1,`session-row`,null,a,{selected:G(h)===G(t).id,wide:G(r)===2,split:G(r)===1}),Q(i,`aria-label`,`Session `+(G(t).name||G(t).directory||G(t).id)),m=li(p,1,`status-detail svelte-mm4wfe`,null,m,{failed:e}),Q(p,`href`,n),_=li(g,1,`lamp lit svelte-mm4wfe`,null,_,{failed:o,working:G(t).status===`WORKING`&&!G(v)}),Y(b,s),Y(E,`${c??``} `),Y(ne,`${G(t).agents??``} LINKED AGENTS`),Y(re,`ACTIVE // ${l??``}`),oe.disabled=G(r)===0,Q(se,`aria-expanded`,G(r)>0),Y(ce,G(r)?`HIDE DETAIL`:`SHOW DETAIL`),Q(ue,`href`,u)},[()=>y(G(t))&&!G(v),()=>`#/sessions/`+encodeURIComponent(G(t).id),()=>y(G(t))&&!G(v),()=>G(v)?`STALE`:l(G(t)),()=>ma(G(t).tokens),()=>ha(G(t).activity),()=>`#/sessions/`+encodeURIComponent(G(t).id)]),K(`click`,p,()=>w(G(t).id)),K(`click`,oe,()=>T(G(t).id,-1)),K(`click`,se,()=>{w(G(t).id),pa(G(t).id,+!G(r))}),K(`click`,le,()=>T(G(t).id,1)),K(`click`,ue,()=>w(G(t).id)),J(e,i)});var f=V(u,2),p=e=>{J(e,hc())};X(f,e=>{G(a).length||e(p)}),K(`click`,s,()=>fa(1)),K(`click`,c,()=>fa(0)),J(e,t)};X(pe,e=>{i().id?e(A):e(me,-1)}),H(e=>{Y(ne,`OBSERVED ${e??``}`),re=li(k,1,`session-totals`,null,re,{stale:G(v)}),Y(ae,`${G(v)?`LAST KNOWN TOTALS — live state counts unavailable. `:``}Tokens - observed since this server started for currently listed sessions; linked - agents are already included. Totals can decrease when a session leaves the - list. History samples every 30 seconds. Not account-wide totals.`)},[()=>ha($.data?.sessionsAt)]),J(e,te),Ge()}Cr([`click`]);var yc=q(`

        `),bc=q(`

        Relative usage/credits are not tokens or quota percentages. Groupings - describe the same usage, not additional consumption.

        `,1),xc=q(`// APPROXIMATE`),Sc=q(`

        Partial accounting: totals may still change.

        `),Cc=q(`

        Percentage of this period's historical allowance, not today's limit. May - exceed 100%. This is not a manual/automatic reset log.

        `,1),wc=q(`

        `,1),Tc=q(`

        This optional account report is unavailable. Token history remains - available; missing data does not mean zero usage.

        `),Ec=q(`
        `),Dc=q(`
        `),Oc=q(`

        No usage reported for this day.

        `),kc=q(`

        Selected breakdown unavailable; missing does not mean zero.

        `),Ac=q(`

        `,1);function jc(e,t){We(t,!0);let n=I(`surface`),r=I(0),i=P(()=>t.reports?.daily),a=P(()=>t.reports?.plan),o=P(()=>t.mode===`breakdown`?G(i)?.days.length||0:G(a)?.periods.length||0),s=P(()=>Math.min(G(r),Math.max(0,G(o)-1))),c=P(()=>G(i)?.days[(G(i)?.days.length||0)-1-G(s)]),l=P(()=>G(a)?.periods[G(s)]),u=P(()=>t.mode===`breakdown`?G(i)?.units||``:`%`),d=P(()=>{let e=Object.create(null);if(t.mode===`breakdown`)e=G(c)?.groups[G(n)]||{};else{let t=G(n)===`feature`?`thread_source`:G(n)===`task start`?`turn_trigger`:G(n);for(let n of G(l)?.breakdowns||[])if(n.dimension===t)for(let t of n.rows)e[t.key]=(e[t.key]||0)+t.basis_points/100}return Object.entries(e).sort((e,t)=>t[1]-e[1]||e[0].localeCompare(t[0]))}),f=P(()=>t.mode===`breakdown`?G(c)?.total:G(l)?.used_basis_points==null?null:G(l).used_basis_points/100),p=P(()=>Math.max(1,G(f)||0,...G(d).map(([,e])=>e))),m=e=>e==null?`UNKNOWN`:e.toLocaleString(`en-GB`,{maximumFractionDigits:2});var h=Ac(),g=z(h),v=R(g),y=V(R(v)),b=R(y);b.value=b.__value=`surface`;var x=V(b);x.value=x.__value=`model`;var S=V(x);S.value=S.__value=`feature`;var C=V(S);C.value=C.__value=`task start`,N(y),hi(y),N(v);var w=V(v,2),T=V(w,2),E=B(V(T,2),!0);N(g);var D=V(g,2),ee=R(D),te=B(ee,!0),O=V(ee,2),ne=B(O),k=V(O,2),re=e=>{var t=bc(),n=z(t),r=B(n),a=V(n,2),o=e=>{var t=yc(),n=B(t);H(()=>Y(n,`Data as of ${G(i).dataAsOf??``}`)),J(e,t)};X(a,e=>{G(i).dataAsOf&&e(o)});var s=B(V(a,2));Se(2),H((e,t)=>{Y(r,`Latest fetch ${e??``} // queried ${G(i).from??``} → ${G(i).through??``}. - Reports are fetched from OpenAI and are not saved between runs.`),Y(s,`${(G(c)?.date||`No daily buckets reported`)??``} // ${t??``} - ${G(u)??``}`)},[()=>ha(G(i).fetchedAt),()=>m(G(f))]),J(e,t)},ie=e=>{var t=wc(),n=z(t),r=R(n);let i;var o=B(r,!0),s=V(r),c=e=>{J(e,xc())};X(s,e=>{G(a).approximate&&e(c)});var u=V(s);N(n);var d=V(n,2),p=R(d),h=V(p),g=e=>{var t=Nr();H(()=>Y(t,`// Boundary tolerance ${G(a).boundary_tolerance_seconds??``}s`)),J(e,t)};X(h,e=>{G(a).boundary_tolerance_seconds!=null&&e(g)}),N(d);var _=V(d,2),v=e=>{var t=Cc(),n=z(t),r=R(n),i=V(r);let a;var o=B(i);N(n);var s=V(n,2),c=B(s),u=V(s,2),d=e=>{J(e,Sc())};X(u,e=>{G(l).accounting_complete||e(d)}),Se(2),H(e=>{Y(r,`${G(l).window_minutes??``} MIN // ${G(l).plan_type??``} // `),a=li(i,1,`svelte-1fbui7h`,null,a,{muted:G(f)==null,"quota-near":G(f)!=null&&G(f)>=80&&G(f)<100,"quota-full":G(f)!=null&&G(f)>=100}),Y(o,`USED ${e??``}${G(f)==null?``:`%`}`),Y(c,`${G(l).starts_at??``} → ${G(l).ends_at??``}`)},[()=>m(G(f))]),J(e,t)};X(_,e=>{G(l)&&e(v)}),H(e=>{i=li(r,1,`svelte-1fbui7h`,null,i,{"quality-warning":!G(a).coverage_complete}),Y(o,G(a).coverage_complete?`Reported range complete`:`Partial coverage`),Y(u,` // Latest fetch ${e??``}`),Y(p,`Coverage starts ${(G(a).coverage_start||`unknown`)??``} // Data as of ${(G(a).data_as_of||`unknown`)??``}`)},[()=>ha(G(a).fetchedAt)]),J(e,t)},ae=e=>{J(e,Tc())};X(k,e=>{t.mode===`breakdown`&&G(i)?e(re):t.mode===`periods`&&G(a)?e(ie,1):e(ae,-1)});var oe=V(k,2),se=e=>{var n=Dc(),r=R(n),i=B(V(R(r),2),!0);N(r),Z(V(r,2),17,()=>G(d),qr,(e,n,r)=>{var i=P(()=>_(G(n),2));let a=()=>G(i)[0],o=()=>G(i)[1];var s=Ec();let c;var l=R(s),u=B(l,!0),d=V(l,2),f=R(d);let h;N(d);var g=B(V(d,2));N(s),H((e,n,i)=>{c=li(s,1,`report-row svelte-1fbui7h`,null,c,{unknown:e,alternate:r%2==1}),Q(l,`title`,a()),Y(u,a()),h=di(f,``,h,{width:n}),Y(g,`${i??``}${t.mode===`periods`?`%`:``}`)},[()=>a().trim().toLowerCase()===`unknown`,()=>`${Math.max(0,100*o()/G(p))}%`,()=>m(o())]),J(e,s)}),N(n),H(()=>Y(i,t.mode===`breakdown`?G(u):`QUOTA USED`)),J(e,n)},ce=e=>{J(e,Oc())},le=e=>{J(e,kc())};X(oe,e=>{G(d).length?e(se):t.mode===`breakdown`&&G(c)?.total===0&&G(c)?.groups[G(n)]!=null?e(ce,1):(G(i)&&t.mode===`breakdown`||G(a)&&t.mode===`periods`)&&e(le,2)}),N(D),H(()=>{w.disabled=G(s)>=G(o)-1,T.disabled=G(s)===0,Y(E,G(o)?`${G(s)+1} / ${G(o)}`:`NO DATA`),Y(te,t.mode===`breakdown`?`DAILY BREAKDOWN`:`QUOTA WINDOWS`),Y(ne,`${(t.mode===`breakdown`?t.reports?.dailyStatus||`UNAVAILABLE`:t.reports?.planStatus||`UNAVAILABLE`)??``} // UTC`)}),gi(y,()=>G(n),e=>L(n,e)),K(`click`,w,()=>L(r,G(s)+1)),K(`click`,T,()=>L(r,G(s)-1)),J(e,h),Ge()}Cr([`click`]);var Mc=864e5;function Nc(e,t){let n=new Date(e);n.setUTCDate(1),n.setUTCMonth(n.getUTCMonth()+t);let r=new Date(Date.UTC(n.getUTCFullYear(),n.getUTCMonth()+1,0)).getUTCDate();return n.setUTCDate(Math.min(e.getUTCDate(),r)),n}function Pc(e,t,n){let r=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()));for(let e=0;;e++){let i=Nc(r,-t),a=new Date(i.getTime()+Mc);if(e===n)return{start:a,end:r};r=i}}var Fc=q(``),Ic=q(``,1),Lc=q(`

        History refresh failed. Any displayed history is the last successful - observation.

        `),Rc=q(`

        Token history unavailable; other reports may still be available.

        `),zc=q(``),Bc=q(`
        `),Vc=q(`

        LESS ░ ▒ ▓ █ MORE // HOVER FOR DATE AND TOKENS

        `,1),Hc=q(`
        `,1),Uc=q(` `),Wc=q(`

        LIFETIME TOKENS

        PEAK DAY

        CURRENT STREAK

        DAYS

        LONGEST TURN

        SECONDS

        LONGEST STREAK

        DAYS

        OPENAI // UTC

        Accessible data table
        Date (UTC)Tokens

        `,1),Gc=q(`

        History unavailable or awaiting a matching account observation. Missing - history is not treated as zero usage.

        `),Kc=q(`

        USAGE // ACCOUNT HISTORY

        Account-wide history reported by Codex, not the local Sessions counter. Dates - use UTC. Allowance history is approximate where indicated; reset causes are - not provided.

        `,1);function qc(e,t){We(t,!0);let n=I(`daily`),r=I(12),i=I(0),a=P(()=>{let e=$.data?.usage?.dailyUsageBuckets;if(!e)return[];let{start:t,end:n}=Pc(new Date,G(r),G(i)),a=new Map;for(let t of e)!Number.isFinite(t.tokens)||t.tokens<0||a.set(t.startDate,(a.get(t.startDate)||0)+t.tokens);let o=[];for(let e=t;e<=n;e=new Date(e.getTime()+864e5)){let t=e.toISOString().slice(0,10);o.push({date:t,tokens:a.get(t)||0})}return o}),o=P(()=>Math.max(1,...G(a).map(e=>e.tokens))),s=P(()=>G(a).length?new Date(G(a)[0].date+`T00:00:00Z`).getUTCDay():0),c=P(()=>{if(G(n)===`monthly`){let e=new Map;for(let t of G(a))e.set(t.date.slice(0,7),(e.get(t.date.slice(0,7))||0)+t.tokens);return[...e].map(([e,t])=>({date:e,tokens:t}))}let e=0;return G(a).map(t=>({date:t.date,tokens:e+=t.tokens}))});var l=Kc(),u=V(z(l),4);Z(u,20,()=>[[`daily`,`Daily`],[`monthly`,`Monthly`],[`cumulative`,`Cumulative`],[`breakdown`,`Breakdown`],[`periods`,`Windows`]],qr,(e,t)=>{var r=P(()=>_(t,2));let a=()=>G(r)[0],o=()=>G(r)[1];var s=Fc();let c;var l=B(s,!0);H(()=>{Q(s,`aria-pressed`,G(n)===a()),c=li(s,1,``,null,c,{active:G(n)===a()}),Y(l,o())}),K(`click`,s,()=>{L(n,a(),!0),L(i,0)}),J(e,s)}),N(u);var d=V(u,2),f=R(d),p=e=>{var t=Ic(),n=z(t),a=V(R(n)),o=R(a);o.value=o.__value=6;var s=V(o);s.value=s.__value=12,N(a),hi(a),N(n);var c=V(n),l=V(c);H(()=>l.disabled=G(i)===0),K(`change`,a,()=>L(i,0)),gi(a,()=>G(r),e=>L(r,e)),K(`click`,c,()=>qt(i)),K(`click`,l,()=>qt(i,-1)),J(e,t)};X(f,e=>{G(n)!==`breakdown`&&G(n)!==`periods`&&e(p)}),N(d);var m=V(d,2),h=e=>{J(e,Lc())};X(m,e=>{$.data?.usageError&&e(h)});var g=V(m,2),v=e=>{J(e,Rc())};X(g,e=>{$.data?.usage?.tokenStatus===`UNAVAILABLE`&&G(n)!==`breakdown`&&G(n)!==`periods`&&e(v)});var y=V(g,2),b=e=>{var t=Pr();Kr(z(t),()=>G(n),e=>{{let t=P(()=>$.data?.usage?.reports);jc(e,{get reports(){return G(t)},get mode(){return G(n)}})}}),J(e,t)},x=e=>{var t=Wc(),r=z(t),i=R(r),l=B(V(R(i),2),!0);N(i);var u=V(i,2),d=B(V(R(u),2),!0);N(u);var f=V(u,2),p=V(R(f),2),m=R(p);Se(),N(p),N(f);var h=V(f,2),g=V(R(h),2),_=R(g);Se(),N(g),N(h);var v=V(h,2),y=V(R(v),2),b=R(y);Se(),N(y),N(v),N(r);var x=V(r,4),S=R(x),C=B(S),w=V(S,2),T=e=>{var t=Vc(),n=z(t),r=R(n),i=R(r);Z(i,17,()=>Array(G(s)),qr,(e,t)=>{J(e,zc())}),Z(V(i,2),17,()=>G(a),qr,(e,t)=>{var n=Bc();let r,i;H(e=>{r=li(n,1,`heat-cell`,null,r,{zero:G(t).tokens===0}),Q(n,`title`,e),i=di(n,``,i,{opacity:G(t).tokens?.25+.75*G(t).tokens/G(o):1})},[()=>`${G(t).date}: ${ma(G(t).tokens)} tokens`]),J(e,n)}),N(r),N(n),Se(2),J(e,t)},E=e=>{var t=Hc(),r=z(t);{let e=P(()=>G(c).map(e=>e.tokens)),t=P(()=>G(n)+` usage`);ao(r,{get values(){return G(e)},get label(){return G(t)}})}var i=V(r,2),a=R(i),o=B(a,!0),s=B(V(a),!0);N(i),H(e=>{Y(o,G(c)[0]?.date),Y(s,e)},[()=>G(c).at(-1)?.date]),J(e,t)};X(w,e=>{G(n)===`daily`?e(T):e(E,-1)});var D=V(w,2),ee=V(R(D),2),te=R(ee),O=V(R(te));Z(O,21,()=>G(n)===`daily`?G(a):G(c),qr,(e,t)=>{var n=Uc(),r=R(n),i=B(r,!0),a=B(V(r),!0);N(n),H(e=>{Y(i,G(t).date),Y(a,e)},[()=>ma(G(t).tokens)]),J(e,n)}),N(O),N(te),N(ee),N(D),N(x);var ne=B(V(x,2));H((e,t,n,r,i,o,s)=>{Y(l,e),Y(d,t),Y(m,`${n??``} `),Y(_,`${r??``} `),Y(b,`${i??``} `),Y(C,`${G(a)[0]?.date??``} — ${o??``}`),Y(ne,`OBSERVED ${s??``} // CUMULATIVE IS FOR THE SELECTED PERIOD`)},[()=>ma($.data.usage.summary.lifetimeTokens),()=>ma($.data.usage.summary.peakDailyTokens),()=>ma($.data.usage.summary.currentStreakDays),()=>ma($.data.usage.summary.longestRunningTurnSec),()=>ma($.data.usage.summary.longestStreakDays),()=>G(a).at(-1)?.date,()=>ha($.data.usageAt)]),J(e,t)},S=e=>{J(e,Gc())};X(y,e=>{G(n)===`breakdown`||G(n)===`periods`?e(b):$.data?.usage&&$.data.usage.dailyUsageBuckets!==null?e(x,1):e(S,-1)}),J(e,l),Ge()}Cr([`click`,`change`]);var Jc=q(`

        Page not found

        Return to Quota

        `,1);function Yc(e){var t=Jc();Se(2),J(e,t)}var Xc=`CODEXOMETER`,Zc=[`█▀▀ █▀█ █▀▄ █▀▀ ▀▄▀ █▀█ █▀▄▀█ █▀▀ ▀█▀ █▀▀ █▀█`,`█▄▄ █▄█ █▄▀ ██▄ █ █ █▄█ █ ▀ █ ██▄ █ ██▄ █▀▄`].flatMap(e=>[[...e].map(e=>e===`█`||e===`▀`),[...e].map(e=>e===`█`||e===`▄`)]),Qc=Zc[0].length,$c=Zc.length,el=0,tl=[...Xc].map(e=>{let t=e===`M`?5:3,n={start:el,width:t};return el+=t+1,n}),nl=`ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`,rl=`010 101 111 101.110 111 101 110.111 100 100 111.110 101 101 110.111 100 110 111.111 100 110 100.111 100 101 111.101 101 111 101.111 010 010 111.001 001 101 111.101 110 110 101.100 100 100 111.101 111 101 101.101 111 111 101.111 101 101 111.111 101 111 100.111 101 111 001.111 101 110 101.111 100 001 111.111 010 010 010.101 101 101 111.101 101 101 010.101 101 111 101.101 010 010 101.101 101 010 010.111 001 100 111.111 101 101 111.110 010 010 111.111 001 110 111.111 011 001 111.101 101 111 001.111 110 001 111.100 111 101 111.111 001 010 010.111 111 101 111.111 101 111 001`.split(`.`).map(e=>e.split(` `));function il(e=Xc,t=-1,n=!1){let r=``;for(let i=0;i<$c;i++)for(let a=0;a{e|=0,e=e+1831565813|0;let t=Math.imul(e^e>>>15,1|e);return t^=t+Math.imul(t^t>>>7,61|t),((t^t>>>14)>>>0)/4294967296}}function cl(){let e=al[Math.floor(Math.random()*al.length)],t=Math.floor(Math.random()*4294967296),n=sl(t),r=[...Xc].map((e,t)=>t);for(let e=r.length-1;e>0;e--){let t=Math.floor(n()*(e+1));[r[e],r[t]]=[r[t],r[e]]}return{variant:e,seed:t,order:r}}function ll(e,t){if(e.variant===`typing`){let e=t<1080,n=e?0:Math.min(Math.floor((t-1080)/90)+1,11);return{text:Xc.slice(0,n).padEnd(11),cursor:n,dot:e?Math.floor(t/180)%2==0:n<11}}if(e.variant===`shuffle`){let n=sl(e.seed^Math.floor(t/70)),r=[...Xc].map(e=>{let t;do t=nl[Math.floor(n()*36)];while(t===e);return t}),i=Math.min(Math.floor(t/110),11);for(let t of e.order.slice(0,i))r[t]=Xc[t];return{text:r.join(``),cursor:-1,dot:!1}}return{text:Xc,cursor:-1,dot:!1}}var ul=Mr(``);function dl(e,t){We(t,!0);let n=Gi(t,`text`,3,Xc),r=Gi(t,`cursor`,19,()=>-1),i=Gi(t,`dot`,3,!1),a=P(()=>il(n(),r(),i()));var o=ul(),s=B(o);H(()=>{Q(o,`viewBox`,`0 0 ${Qc} ${$c}`),Q(o,`width`,Qc),Q(o,`height`,$c),Q(s,`d`,G(a))}),J(e,o),Ge()}var fl=q(`
        `),pl=q(`
        `);function ml(e,t){We(t,!0);let n=I(void 0),r=I(0),i=I(Xt({width:0,height:0,pixelRatio:1})),a=I(Xt({x:0,y:0,width:0})),o=P(()=>G(n)?ll(G(n),G(r)):void 0),s=P(()=>{if(!G(n))return{x:0,y:0,width:0};let e=ol[G(n).variant],t=Math.min(Math.max((G(r)-e-180)/800,0),1),o=t*t*(3-2*t),s=G(i).width+(G(a).width-G(i).width)*o,c=Math.max((G(i).height-G(i).width*$c/Qc)/2,0),l=(G(n).variant===`slide`&&G(r)Math.round(e*G(i).pixelRatio)/G(i).pixelRatio;return{x:d(l),y:d(u),width:d(s)}});Ki(()=>{let e=matchMedia(`(prefers-reduced-motion: reduce)`);if(e.matches){t.onfinish();return}L(n,cl(),!0);let o=ol[G(n).variant]+180+800,s=0,c=!1,l=performance.now(),u=()=>{let e=t.target?.getBoundingClientRect();L(i,{width:window.innerWidth,height:window.innerHeight,pixelRatio:window.devicePixelRatio||1},!0),e&&L(a,{x:e.x,y:e.y,width:e.width},!0)},d=()=>{c||(c=!0,cancelAnimationFrame(s),t.onfinish())},f=e=>{e instanceof PointerEvent&&e.button!==0||e instanceof KeyboardEvent&&(e.metaKey||e.ctrlKey||e.altKey||e.shiftKey)||e instanceof KeyboardEvent&&[`Shift`,`Control`,`Alt`,`Meta`].includes(e.key)||(e.preventDefault(),e.stopImmediatePropagation(),d())},p=()=>{e.matches&&d()},m=e=>{L(r,Math.max(e-l,0),!0),G(r)>=o?d():s=requestAnimationFrame(m)},h=new ResizeObserver(u);if(t.target){h.observe(t.target),t.target.parentElement&&h.observe(t.target.parentElement);let e=t.target.closest(`header`);e&&h.observe(e)}return u(),window.addEventListener(`resize`,u),document.addEventListener(`keydown`,f,!0),document.addEventListener(`pointerdown`,f,!0),e.addEventListener(`change`,p),s=requestAnimationFrame(m),()=>{c=!0,cancelAnimationFrame(s),h.disconnect(),window.removeEventListener(`resize`,u),document.removeEventListener(`keydown`,f,!0),document.removeEventListener(`pointerdown`,f,!0),e.removeEventListener(`change`,p)}});var c=pl(),l=R(c),u=e=>{var t=fl();let n;dl(R(t),{get text(){return G(o).text},get cursor(){return G(o).cursor},get dot(){return G(o).dot}}),N(t),H(()=>{Q(t,`data-text`,G(o).text),Q(t,`data-cursor`,G(o).dot?G(o).cursor:void 0),n=di(t,``,n,{left:`${G(s).x}px`,top:`${G(s).y}px`,width:`${G(s).width}px`})}),J(e,t)};X(l,e=>{G(o)&&e(u)});var d=V(l,2);N(c),H(()=>Q(c,`data-entrance`,G(n)?.variant)),K(`click`,d,function(...e){t.onfinish?.apply(this,e)}),J(e,c),Ge()}Cr([`click`]);var hl=q(` `),gl=q(`

        `),_l=q(`

        Connecting to your local Codexometer…

        `),vl=q(``),yl=q(`

        Your quota. Your sessions. Your command centre.

        `);function bl(e,t){We(t,!0);let n={"/":no,"/quota/:view?":no,"/sessions/:id?":vc,"/usage":qc,"*":Yc},r={quota:/^(?:\/|\/quota(?:\/[^/]+)?\/?)$/,sessions:/^\/sessions(?:\/[^/]+)?\/?$/,usage:/^\/usage\/?$/},i=I(`hacker`),a=I(!0),o=I(void 0);yn(()=>{ca.requests&&ur().then(()=>{G(o)?.focus(),G(o)?.scrollIntoView({block:`nearest`})})});let s=I(void 0),c=[`hacker`,`rust`,`blue-steel`,`ultraviolet`,`nightshade`];yn(()=>{la()}),yn(()=>{if($.data)for(let[e,t]of Object.entries(r))t.test(Xi.location)&&(oa.tab=e)}),Ki(()=>{try{let e=localStorage.getItem(`codexometer.web.theme`);e&&c.includes(e)&&L(i,e,!0)}catch{}return ba()});function l(){try{localStorage.setItem(`codexometer.web.theme`,G(i))}catch{}}var u=yl(),d=R(u),f=R(d),p=R(f);dl(R(p),{}),N(p),Li(p,e=>L(s,e),()=>G(s)),Se(2),N(f);var m=V(f,2),h=R(m);let g;var v=V(h,1,!0),y=B(V(v));N(m),N(d);var b=V(d,2);Z(b,21,()=>Object.entries(r),qr,(e,t)=>{var n=P(()=>_(G(t),2));let r=()=>G(n)[0],i=()=>G(n)[1],a=P(()=>i().test(Xi.location));var o=hl();let s;var c=B(o,!0);H(e=>{Q(o,`href`,r()===`quota`?`#/quota/`+oa.view:`#/`+r()),Q(o,`aria-current`,G(a)?`page`:void 0),s=li(o,1,``,null,s,{active:G(a)}),Y(c,e)},[()=>r().toUpperCase()]),J(e,o)}),N(b);var x=V(b,2),S=R(x),C=e=>{var t=gl(),n=B(t,!0);H(()=>Y(n,$.error)),J(e,t)};X(S,e=>{$.error&&e(C)});var w=V(S,2),T=e=>{ea(e,{get routes(){return n}})},E=e=>{J(e,_l())};X(w,e=>{$.data?e(T):$.error||e(E,1)}),N(x);var D=V(x,2),ee=R(D),te=B(ee),O=V(ee,2),ne=B(O,!0),k=V(O,2),re=V(R(k));Z(re,21,()=>c,qr,(e,t)=>{var n=vl(),r=B(n,!0),i={};H(e=>{Y(r,e),i!==(i=G(t))&&(n.value=(n.__value=i)??``)},[()=>G(t).replace(`-`,` `).toUpperCase()]),J(e,n)}),N(re),Li(re,e=>L(o,e),()=>G(o)),hi(re),N(k),N(D);var ie=V(D,2),ae=e=>{ml(e,{get target(){return G(s)},onfinish:()=>L(a,!1)})};X(ie,e=>{G(a)&&e(ae)}),N(u),H(()=>{Q(u,`data-theme`,G(i)),Q(u,`data-starting`,G(a)),d.inert=G(a),g=li(h,1,`lamp`,null,g,{lit:$.connected}),Y(v,$.connected?`CONNECTED`:`OFFLINE`),Y(y,`EXPERIMENTAL // ${$.data?.control?`SESSION CONTROL`:`READ ONLY`}`),b.inert=G(a),x.inert=G(a),D.inert=G(a),Y(te,`v${($.data?.version||`…`)??``} // LOCAL ONLY`),Y(ne,$.data?.control?`Session control enabled. Stop the server to revoke access.`:`No actions can be sent from this preview.`)}),K(`change`,re,l),gi(re,()=>G(i),e=>L(i,e)),J(e,u),Ge()}Cr([`change`]),Br(bl,{target:document.getElementById(`app`)}); \ No newline at end of file diff --git a/internal/web/dist/index.html b/internal/web/dist/index.html index 6d1f4e4f..c3e309aa 100644 --- a/internal/web/dist/index.html +++ b/internal/web/dist/index.html @@ -5,7 +5,7 @@ Codexometer // Experimental web - + diff --git a/internal/web/projection_fixtures_test.go b/internal/web/projection_fixtures_test.go new file mode 100644 index 00000000..887c2459 --- /dev/null +++ b/internal/web/projection_fixtures_test.go @@ -0,0 +1,174 @@ +package web + +import ( + "encoding/json" + "fmt" + "math" + "os" + "reflect" + "sort" + "testing" + "time" + + "github.com/merefield/codexometer/internal/codex" + "github.com/merefield/codexometer/internal/quotagraph" +) + +// These checked-in browser fixtures are produced by the very Go projectors +// used by the TUI/server, not a second JavaScript implementation in the tests. +func TestBrowserProjectionFixtures(t *testing.T) { + now := time.Date(2026, 9, 11, 12, 0, 0, 0, time.UTC) + meters := map[string]meter{} + add := func(name string, used int, duration int64, reset time.Time, trail []quotagraph.Point) { + r := reset.Unix() + w := codex.Window{UsedPercent: used, WindowDurationMins: &duration, ResetsAt: &r} + meters[name] = meter{Name: name, Used: used, Duration: &duration, Reset: &r, Trail: trail, Plot: quotagraph.ProjectPlot(w, trail, now)} + } + point := func(hoursAgo, elapsed float64, used int) quotagraph.Point { + return quotagraph.Point{At: now.Add(-time.Duration(hoursAgo * float64(time.Hour))), Elapsed: elapsed, Used: used} + } + weekly := int64(10080) + end := now.Add(72 * time.Hour) + add("weeklyTrace", 40, weekly, end, []quotagraph.Point{point(25, 42, 20), point(24, 42.6, 21), point(1, 56.5, 38), point(.25, 56.95, 39), point(0, 57.1, 40)}) + add("weeklyEmpty", 40, weekly, end, nil) + add("weeklyZero", 0, weekly, end, nil) + add("weeklyStart", 0, weekly, now.Add(168*time.Hour), nil) + elapsed := 400.0 / 7 + delta := 50.0 / 168 + add("overWindow", 80, weekly, end, []quotagraph.Point{point(0, elapsed, 80)}) + add("overRise", 80, weekly, end, []quotagraph.Point{point(.5, elapsed-delta, 79), point(0, elapsed, 80)}) + add("overFlat", 80, weekly, end, []quotagraph.Point{point(.5, elapsed-delta, 80), point(0, elapsed, 80)}) + add("boundary", 50, weekly, now.Add(84*time.Hour), nil) + add("start", 0, 100, now.Add(125*time.Minute), nil) + add("fast", 75, 100, now.Add(75*time.Minute), nil) + add("end", 100, 100, now.Add(-50*time.Minute), nil) + meters["unknown"] = meter{Name: "unknown", Used: 40, Plot: quotagraph.ProjectPlot(codex.Window{}, nil, now)} + used := 12500.0 + reports := &codex.UsageReports{DailyStatus: "OPENAI", PlanStatus: "OPENAI", Daily: &codex.DailyUsageReport{Units: "RELATIVE USAGE", From: "2026-09-04", Through: "2026-10-03", FetchedAt: time.Date(2026, 10, 3, 12, 0, 0, 0, time.UTC), Days: []codex.UsageBreakdownDay{ + {Date: "2026-10-01", Total: 2, Groups: map[string]map[string]float64{"model": {"older-model": 2}}}, + {Date: "2026-10-02", Total: 4, Groups: map[string]map[string]float64{"model": {"gpt-6.1-sol": 2.5, "gpt-6-luna": 1.5}, "surface": {"cli": 4}}}, + }}, Plan: &codex.PlanUsageReport{FetchedAt: time.Date(2026, 10, 3, 12, 0, 0, 0, time.UTC), DataAsOf: "2026-10-03T11:00:00Z", CoverageStart: "2026-09-26T00:00:00Z", Approximate: true, BoundaryTolerance: new(uint32(60)), Periods: []codex.PlanUsagePeriod{ + {ID: "p1", WindowMinutes: 10080, PlanType: "pro", StartsAt: "2026-09-28T00:00:00Z", EndsAt: "2026-10-05T00:00:00Z", UsedBasisPoints: &used, Breakdowns: []codex.PlanUsageBreakdown{{Dimension: "model", Rows: []codex.PlanUsageValue{{Key: "gpt-6.1-sol", BasisPoints: 12000}, {Key: "unknown", BasisPoints: 500}}}}}, + {ID: "p2", WindowMinutes: 300, PlanType: "pro", StartsAt: "2026-09-27T00:00:00Z", EndsAt: "2026-09-27T05:00:00Z"}, + }}} + fractional := &codex.UsageReports{Daily: &codex.DailyUsageReport{Units: "RELATIVE USAGE", Days: []codex.UsageBreakdownDay{{Date: "2026-10-02", Total: .1, Groups: map[string]map[string]float64{"surface": {"cli": .05, "desktop": .05}}}}}} + correctionPlan := *reports.Plan + correctionPeriod := correctionPlan.Periods[0] + correctionPeriod.UsedBasisPoints = new(-25.0) + correctionPeriod.Breakdowns = []codex.PlanUsageBreakdown{{Dimension: "surface", Rows: []codex.PlanUsageValue{{Key: "cli", BasisPoints: -25}}}} + correctionPlan.Periods = []codex.PlanUsagePeriod{correctionPeriod} + correction := &codex.UsageReports{PlanStatus: "OPENAI", Plan: &correctionPlan} + warningPlan, warningPeriod := correctionPlan, correctionPeriod + warningPeriod.UsedBasisPoints = new(8000.0) + warningPeriod.Breakdowns = []codex.PlanUsageBreakdown{{Dimension: "surface", Rows: []codex.PlanUsageValue{{Key: "cli", BasisPoints: 8000}}}} + warningPlan.Periods = []codex.PlanUsagePeriod{warningPeriod} + warning := &codex.UsageReports{PlanStatus: "OPENAI", Plan: &warningPlan} + data, err := json.MarshalIndent(map[string]any{"meters": meters, "reports": codex.ProjectUsageReports(reports), "fractional": codex.ProjectUsageReports(fractional), "correction": codex.ProjectUsageReports(correction), "warning": codex.ProjectUsageReports(warning)}, "", " ") + if err != nil { + t.Fatal(err) + } + data = append(data, '\n') + const path = "../../web/tests/projections.generated.json" + if os.Getenv("CODEXOMETER_WEB_GENERATE") == "1" { + if err := os.WriteFile(path, data, 0o644); err != nil { + t.Fatal(err) + } + } + got, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + var actual, expected any + if err := json.Unmarshal(got, &actual); err != nil { + t.Fatal("invalid browser numerical fixtures", err) + } + if err := json.Unmarshal(data, &expected); err != nil { + t.Fatal(err) + } + if err := compareProjectionValues(actual, expected, "$"); err != nil { + t.Fatal("browser numerical fixtures are stale; run make web-contracts:", err) + } +} + +// Fused multiply-add on ARM64 and FMA-capable amd64 can change the final bits +// of graph coordinates. Compare JSON structure exactly, allowing only rounding +// noise in numbers; never change production maths to make fixture bytes agree. +func compareProjectionValues(got, want any, path string) error { + switch expected := want.(type) { + case float64: + if actual, ok := got.(float64); ok { + tolerance := (16 * 0x1p-52) * max(1, math.Abs(expected)) + if math.Abs(actual-expected) <= tolerance { + return nil + } + } + case map[string]any: + if actual, ok := got.(map[string]any); ok && len(actual) == len(expected) { + keys := make([]string, 0, len(expected)) + for key := range expected { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + value, present := actual[key] + if !present { + return fmt.Errorf("%s.%s: missing field", path, key) + } + if err := compareProjectionValues(value, expected[key], path+"."+key); err != nil { + return err + } + } + return nil + } + case []any: + if actual, ok := got.([]any); ok && len(actual) == len(expected) { + for i := range expected { + if err := compareProjectionValues(actual[i], expected[i], fmt.Sprintf("%s[%d]", path, i)); err != nil { + return err + } + } + return nil + } + default: + if reflect.DeepEqual(got, want) { + return nil + } + } + return fmt.Errorf("%s: got %v, want %v", path, got, want) +} + +func TestProjectionFixtureComparison(t *testing.T) { + for _, tc := range []struct { + name, got, want string + equal bool + }{ + {"FMA roundoff", `{"segment":{"y1":79.00000000000001,"y2":99.99999999999999}}`, `{"segment":{"y1":79,"y2":100}}`, true}, + {"regression roundoff", `{"y":38.015873015872984}`, `{"y":38.015873015872955}`, true}, + {"coordinate change", `{"y":79.00000001}`, `{"y":79}`, false}, + {"small value change", `{"total":0.05000001}`, `{"total":0.05}`, false}, + {"zero changed", `{"y":0.00000001}`, `{"y":0}`, false}, + {"window change", `{"duration":10081}`, `{"duration":10080}`, false}, + {"safety changed", `{"safe":false}`, `{"safe":true}`, false}, + {"label changed", `{"name":"new"}`, `{"name":"old"}`, false}, + {"missing field", `{}`, `{"segment":null}`, false}, + {"different field", `{"other":null}`, `{"segment":null}`, false}, + {"extra field", `{"segment":null,"other":null}`, `{"segment":null}`, false}, + {"missing row", `[]`, `[1]`, false}, + {"row order", `[1,2]`, `[2,1]`, false}, + {"missing versus empty", `null`, `[]`, false}, + {"number versus string", `"79"`, `79`, false}, + } { + t.Run(tc.name, func(t *testing.T) { + var got, want any + if err := json.Unmarshal([]byte(tc.got), &got); err != nil { + t.Fatal(err) + } + if err := json.Unmarshal([]byte(tc.want), &want); err != nil { + t.Fatal(err) + } + if err := compareProjectionValues(got, want, "$"); (err == nil) != tc.equal { + t.Fatalf("equal=%v: %v", tc.equal, err) + } + }) + } +} diff --git a/internal/web/schedules.go b/internal/web/schedules.go index 5aa003f1..5da8d3fd 100644 --- a/internal/web/schedules.go +++ b/internal/web/schedules.go @@ -26,6 +26,12 @@ type triggerSummary struct { Status string `json:"status"` } +type scheduledJob struct { + schedule.Job + CanSend bool `json:"canSend"` + WaitingReason string `json:"waitingReason"` +} + func (c *control) scheduleSnapshot() codex.Snapshot { c.store.mu.Lock() defer c.store.mu.Unlock() @@ -76,12 +82,7 @@ func (c *control) handleSchedules(w http.ResponseWriter, r *http.Request, b acti c.publishSchedules() } w.Header().Set("Content-Type", "application/json") - type visibleJob struct { - schedule.Job - CanSend bool `json:"canSend"` - WaitingReason string `json:"waitingReason"` - } - jobs := []visibleJob{} + jobs := []scheduledJob{} o, _ := c.offer(b.Session) for _, j := range c.schedules.List(b.Session) { _, ready := c.sendNowJob(b.Session, j.ID) @@ -102,7 +103,7 @@ func (c *control) handleSchedules(w http.ResponseWriter, r *http.Request, b acti case canSend: reason = "ready; awaiting dispatch" } - jobs = append(jobs, visibleJob{j, canSend, reason}) + jobs = append(jobs, scheduledJob{j, canSend, reason}) } _ = json.NewEncoder(w).Encode(jobs) } diff --git a/internal/web/state.go b/internal/web/state.go index a8f234a7..f8603d92 100644 --- a/internal/web/state.go +++ b/internal/web/state.go @@ -69,28 +69,22 @@ func projectGoal(observed *codex.SessionGoal) *goal { TokenBudget: copy.TokenBudget, TokensUsed: copy.TokensUsed, TimeUsedSeconds: copy.TimeUsedSeconds, Stale: copy.Stale} } -type sample struct { - At time.Time `json:"at"` - Tokens int64 `json:"tokens"` -} +type sample = codex.UsageSample type meter struct { identity meterIdentity - Name string `json:"name"` - Used int `json:"used"` - Duration *int64 `json:"duration"` - Reset *int64 `json:"reset"` - Details string `json:"details"` - Trail []zonePoint `json:"trail"` + Name string `json:"name"` + Used int `json:"used"` + Duration *int64 `json:"duration"` + Reset *int64 `json:"reset"` + Details string `json:"details"` + Trail []zonePoint `json:"trail"` + Plot quotagraph.Plot `json:"plot"` } // Never use presentation labels as identity or expose the source limit IDs. -// Window is the source slot (0 primary, 1 secondary), even when names coincide. -type meterIdentity struct { - limitID string - window int - kind codex.MeterKind -} +// Slot is the source window (0 primary, 1 secondary), even when names coincide. +type meterIdentity = codex.MeterKey type zonePoint = quotagraph.Point @@ -137,10 +131,10 @@ type state struct { Control bool `json:"control"` NoConfirm bool `json:"noConfirm,omitempty"` Version string `json:"version"` - Meters []meter `json:"meters"` - Credits []credit `json:"credits"` + Meters []meter `json:"meters" ts:"nonnull"` + Credits []credit `json:"credits" ts:"nonnull"` CreditCount int `json:"creditCount"` - Sessions []session `json:"sessions"` + Sessions []session `json:"sessions" ts:"nonnull"` Usage *codex.AccountUsage `json:"usage"` QuotaAt time.Time `json:"quotaAt"` SessionsAt time.Time `json:"sessionsAt"` @@ -235,24 +229,11 @@ func (s *store) quota(q codex.Snapshot, err error, now time.Time) { s.account = q.AccountFingerprint s.state.QuotaAt = now s.state.Meters = []meter{} - windows := map[string]int{} for _, m := range q.Meters() { item := meter{Name: m.Bucket + " // " + m.Name, Used: max(0, min(100, m.Window.UsedPercent)), Duration: m.Window.WindowDurationMins, Reset: m.Window.ResetsAt, Details: m.Details} - item.identity = meterIdentity{limitID: m.LimitID, kind: m.Kind} - if m.Kind == codex.MeterQuotaWindow { - bucket := q.RateLimits - if len(q.RateLimitsByLimitID) > 0 { - bucket = q.RateLimitsByLimitID[m.LimitID] - } - // Meters emits primary before secondary. Preserve the secondary - // slot when a previously present primary disappears. - item.identity.window = windows[m.LimitID] - if bucket.Primary == nil { - item.identity.window++ - } - windows[m.LimitID]++ - } + item.identity = m.Key() item.Trail = observeZone(item, previous, now, gap) + item.Plot = quotagraph.ProjectPlot(m.Window, item.Trail, now) s.state.Meters = append(s.state.Meters, item) } s.state.CreditCount = 0 @@ -275,6 +256,7 @@ func (s *store) usage(h codex.AccountUsage, err error, now time.Time) { s.state.UsageError = err != nil if err == nil { s.history = h + s.history.Reports = codex.ProjectUsageReports(h.Reports) s.history.FetchedAt = now } s.reconcileUsage() @@ -327,17 +309,10 @@ func (s *store) live(l codex.LiveUsageSnapshot, err error, now time.Time) { before, known := s.previous[row.ID] if !known || row.TotalTokens < before { s.previous[row.ID] = row.TotalTokens + before = row.TotalTokens } if tick { - delta := int64(0) - if known { - delta = max(0, row.TotalTokens-before) - } - points := append(s.samples[row.ID], sample{At: now, Tokens: delta}) - if len(points) > 120 { - points = points[len(points)-120:] - } - s.samples[row.ID] = points + s.samples[row.ID] = codex.CaptureUsageSample(s.samples[row.ID], row.TotalTokens, before, now, now, 120) s.previous[row.ID] = row.TotalTokens } text := row.Context.Text @@ -391,25 +366,7 @@ func (s *store) live(l codex.LiveUsageSnapshot, err error, now time.Time) { } func sessionStatus(s codex.LiveUsageSession) string { - switch s.Attention { - case codex.SessionAttentionInput: - return "INPUT NEEDED" - case codex.SessionAttentionApproval: - return "APPROVAL NEEDED" - case codex.SessionAttentionCheck: - return "CHECK SESSION" - case codex.SessionAttentionError: - return codex.SessionFailureLabel(s.Context) - case codex.SessionAttentionComplete: - return "TURN COMPLETE" - } - if s.Working { - return "WORKING" - } - if !s.Active { - return "INACTIVE" - } - return "IDLE" + return codex.SessionStatus(s.Active, s.Working, s.Attention, s.Context) } func contextKind(k codex.SessionContextKind) string { diff --git a/internal/web/zone_test.go b/internal/web/zone_test.go index 5c9cb77c..b8e43c68 100644 --- a/internal/web/zone_test.go +++ b/internal/web/zone_test.go @@ -74,7 +74,7 @@ func TestZoneTrailBoundariesAndBound(t *testing.T) { "reset": func(m *meter) { r := reset + 100; m.Reset = &r }, "duration": func(m *meter) { d := duration + 1; m.Duration = &d }, "counter decrease": func(m *meter) { m.Used = 9 }, - "different window": func(m *meter) { m.identity.window = 1 }, + "different window": func(m *meter) { m.identity.Slot = 1 }, } { t.Run(name, func(t *testing.T) { next := m diff --git a/web/.prettierignore b/web/.prettierignore index a9bb02ac..9a21e13d 100644 --- a/web/.prettierignore +++ b/web/.prettierignore @@ -2,3 +2,5 @@ node_modules test-results playwright-report package-lock.json +src/contracts.generated.ts +tests/projections.generated.json diff --git a/web/src/ConsumptionPace.svelte b/web/src/ConsumptionPace.svelte index 1bd32c85..c6e4cad3 100644 --- a/web/src/ConsumptionPace.svelte +++ b/web/src/ConsumptionPace.svelte @@ -3,23 +3,22 @@ import { date } from './state.svelte'; let { used, - elapsed, + elapsed: fallbackElapsed, trail = [], - duration, + plot, }: { used: number; elapsed: number; - trail?: Meter['trail']; - duration: number | null; + trail?: NonNullable; + plot?: Meter['plot']; } = $props(); - type TrailPoint = NonNullable[number]; type TrendMode = 'off' | 'halfHour' | 'hour' | 'day' | 'window'; const gradient = $props.id(); let showObservations = $state(false); let showTrace = $state(true); let trendMode = $state('window'); const ticks = [0, 25, 50, 75, 100]; - const trendMinutes = { off: 0, halfHour: 30, hour: 60, day: 1440, window: 0 }; + let elapsed = $derived(plot?.known ? plot.elapsed : fallbackElapsed); const minimum = 0; const range = 100; let width = $state(400); @@ -61,69 +60,19 @@ Date.parse(continuous.at(-1)!.at) - Date.parse(continuous[0].at), ); }); - function observedPeriod(span: number): boolean { - if (observedMilliseconds < span || continuous.length < 2) return false; - const cutoff = Date.parse(continuous.at(-1)!.at) - span; - return ( - continuous.filter((point) => Date.parse(point.at) >= cutoff).length >= 2 - ); - } - let availability = $derived({ - off: true, - halfHour: observedPeriod(30 * 60_000), - hour: observedPeriod(60 * 60_000), - day: observedPeriod(24 * 60 * 60_000), - window: Number.isFinite(elapsed) && elapsed > 0 && Number.isFinite(used), - }); + let availability = $derived( + Object.fromEntries( + (plot?.modes || []).map((mode) => [mode.id, mode.available]), + ), + ); $effect(() => { if (trendMode !== 'window' && !availability[trendMode]) trendMode = 'window'; }); - function trendPoints(mode: TrendMode): TrailPoint[] { - if (mode === 'off' || !availability[mode]) return []; - const span = trendMinutes[mode] * 60_000; - const cutoff = Date.parse(continuous.at(-1)!.at) - span; - const start = continuous.findIndex( - (point) => Date.parse(point.at) >= cutoff, - ); - if (start < 0) return []; - return continuous.slice(start); - } - function regressionSlope(points: TrailPoint[]): number | null { - if (points.length < 2) return null; - const xMean = - points.reduce((sum, point) => sum + point.elapsed, 0) / points.length; - const yMean = - points.reduce((sum, point) => sum + point.used, 0) / points.length; - let covariance = 0; - let variance = 0; - for (const point of points) { - covariance += (point.elapsed - xMean) * (point.used - yMean); - variance += (point.elapsed - xMean) ** 2; - } - return variance > 0 ? Math.max(0, covariance / variance) : null; - } let trend = $derived.by(() => { - const slope = - trendMode === 'window' && availability.window - ? used / elapsed - : regressionSlope(trendPoints(trendMode)); - if (slope === null || !duration) return null; - const projected = used + slope * (100 - elapsed); - const intercept = trendMode === 'window' ? 0 : used - slope * elapsed; - const graphSlope = slope; - let x1 = Math.max(0, elapsed - (trendMinutes[trendMode] / duration) * 100); - if (trendMode === 'window') x1 = 0; - let x2 = 100; - if (graphSlope !== 0) { - const lower = (minimum - intercept) / graphSlope; - const upper = (minimum + range - intercept) / graphSlope; - x1 = Math.max(x1, Math.min(lower, upper)); - x2 = Math.min(100, Math.max(lower, upper)); - } - if (x1 > x2) return null; - const y1 = intercept + graphSlope * x1; - const y2 = intercept + graphSlope * x2; + const projection = plot?.modes.find((mode) => mode.id === trendMode); + if (!projection?.segment) return null; + const { x1, y1, x2, y2 } = projection.segment; const pixelLength = Math.hypot( ((x2 - x1) / 100) * plotWidth, ((y2 - y1) / range) * plotHeight, @@ -136,8 +85,8 @@ }); return { path: `M${points.join(' L')}`, - projected, - safe: projected <= 100, + projected: projection.projected, + safe: projection.safe, }; }); let observedLabel = $derived.by(() => { diff --git a/web/src/GoalStatus.svelte b/web/src/GoalStatus.svelte index d6aa8a10..566eb38d 100644 --- a/web/src/GoalStatus.svelte +++ b/web/src/GoalStatus.svelte @@ -1,6 +1,6 @@ diff --git a/web/src/Quota.svelte b/web/src/Quota.svelte index 95463225..52006fb0 100644 --- a/web/src/Quota.svelte +++ b/web/src/Quota.svelte @@ -27,6 +27,7 @@ return () => clearInterval(timer); }); function elapsed(m: Meter): number | null { + // Unlike observed Pace geometry, reset timelines advance on the live clock. if (!m.duration || m.duration <= 0 || !m.reset) return null; return Math.max( 0, @@ -135,7 +136,7 @@ used={meter.used} elapsed={cycle} trail={meter.trail || []} - duration={meter.duration} + plot={meter.plot} /> {:else}

        Cycle duration or reset date unavailable — position cannot be diff --git a/web/src/SessionActions.svelte b/web/src/SessionActions.svelte index 5717ef03..430a7223 100644 --- a/web/src/SessionActions.svelte +++ b/web/src/SessionActions.svelte @@ -23,25 +23,10 @@ onProtectedChange?: (protectedState: boolean) => void; oncopy?: () => void | Promise; } = $props(); - interface Offer { - fileChanges?: FileDiffLine[]; - profile?: { threshold: number; current: string; proposed: string }; - id: string; - session: string; - thread: string; - directory: string; - kind: string; - command: string; - choices: { label: string; detail: string; persistent: boolean }[] | null; - questions: - | { - text: string; - secret: boolean; - freeText: boolean; - options: string[] | null; - }[] - | null; - } + import type { + ActionOffer as Offer, + ScheduledJob as Job, + } from './contracts.generated'; let offer = $state(null); let answers = $state([]); let choice = $state(null); @@ -62,16 +47,6 @@ let dateTime = $state(''); let scheduled = $state(false); let triggerDescription = $state(''); - interface Job { - id: string; - session: string; - text: string; - trigger: string; - at: string; - status: string; - canSend?: boolean; - waitingReason?: string; - } let jobs = $state([]); let pending = $derived(jobs.find((j) => j.status !== 'sent')); let composer: HTMLTextAreaElement | undefined = $state(); @@ -96,7 +71,7 @@ timing = job.trigger === 'quota' ? 'quota' : 'at'; confirmation = ''; sendNowID = ''; - const d = new Date(job.at); + const d = new Date(job.at || 0); if (job.trigger !== 'quota') dateTime = new Date(d.getTime() - d.getTimezoneOffset() * 60000) .toISOString() @@ -224,12 +199,13 @@ ? 'Choose a time within one year.' : ''; }); - function localTime(value: number | string) { + function localTime(value: number | string | undefined) { + if (value === undefined) return 'Not yet available'; return new Date(value).toLocaleString([], { timeZoneName: 'short' }); } function waitingText(job: Job) { if (job.status !== 'pending') return job.status; - if (job.trigger === 'at' && Date.parse(job.at) > now) + if (job.trigger === 'at' && job.at && Date.parse(job.at) > now) return 'Due in ' + Math.ceil((Date.parse(job.at) - now) / 60000) + ' min'; const reason = stale ? 'waiting for a fresh connection' diff --git a/web/src/SessionCommands.svelte b/web/src/SessionCommands.svelte index 93163c52..1dea3efb 100644 --- a/web/src/SessionCommands.svelte +++ b/web/src/SessionCommands.svelte @@ -18,69 +18,21 @@ onchange?: () => void; oncomplete?: (text: string) => void; } = $props(); - interface Choice { - id: string; - label: string; - help: string; - next?: string; - action?: boolean; - reversible?: boolean; - selected?: boolean; - unavailable?: string; - } - interface Menu { - title: string; - help: string; - path: string; - revision: string; - choices: Choice[]; - input?: boolean; - reversibleInput?: boolean; - picker?: boolean; - inputLabel?: string; - inputLimit?: number; - value?: string; - } - const localPaths = ['statusline', 'usage', 'copy', 'theme', 'exit', 'quit']; + import { + localCommands, + type CommandChoice as Choice, + type CommandMenu as Menu, + } from './contracts.generated'; + const localPaths = localCommands.map((choice) => choice.next); function localChoices(): Choice[] { - return [ - { - id: 'local-statusline', - label: '/statusline', - help: 'Choose fields for Codexometer’s detail footer.', - next: 'statusline', - }, - { - id: 'local-usage', - label: '/usage', - help: 'Open Codexometer’s usage history. Quota and reset views remain available.', - next: 'usage', - }, - { - id: 'local-copy', - label: '/copy', - help: 'Copy the selected session’s observed reply or activity with its Copy control.', - next: 'copy', - }, - { - id: 'local-theme', - label: '/theme', - help: 'Open the existing Codexometer theme selector. Saved browser display preferences only.', - next: 'theme', - }, - { - id: 'local-exit', - label: '/exit', - help: 'Leave session detail and return to Sessions. The local server remains running.', - next: 'exit', - }, - { - id: 'local-quit', - label: '/quit', - help: 'Alias for /exit: return to Sessions. The local server remains running.', - next: 'quit', - }, - ]; + return localCommands.map((choice) => + ['exit', 'quit'].includes(choice.next || '') + ? { + ...choice, + help: 'Return to Sessions. The local server and Codex sessions remain running.', + } + : { ...choice }, + ); } function localMenu(): Menu { return { @@ -152,12 +104,7 @@ input: string; } let history = $state([]); - function isInputReview(path: string) { - return ['rename/', 'cd/', 'goal/set/', 'goal/edit/', 'goal/budget/'].some( - (prefix) => path.startsWith(prefix), - ); - } - const inputReview = $derived(isInputReview(menu?.path || '')); + const inputReview = $derived(!!menu?.inputReview); const canBack = $derived( (!!selected && !inputReview) || !!stagedChoice || history.length > 0, ); @@ -435,6 +382,7 @@ command: { mode: 'list', path, ...(refresh ? { refresh: true } : {}) }, }); if (seq === request) { + result.choices ||= []; if (path === '' || path === 'help') result.choices = [ ...result.choices.filter( @@ -458,7 +406,7 @@ if (result.input) commandPanel?.querySelector('input')?.focus(); else optionList?.focus(); }); - if (isInputReview(result.path) && result.choices.length === 1) { + if (result.inputReview && result.choices.length === 1) { busy = false; // The catalogue is ready before preparing its sole choice. void choose(result.choices[0]); } diff --git a/web/src/UsageReports.svelte b/web/src/UsageReports.svelte index af61c99f..b99536b8 100644 --- a/web/src/UsageReports.svelte +++ b/web/src/UsageReports.svelte @@ -1,48 +1,29 @@