diff --git a/README.md b/README.md index 6156f81..96107e1 100644 --- a/README.md +++ b/README.md @@ -462,6 +462,12 @@ Set up the recommended arrangement as follows. reconstructed after the fact. No additional Codexometer option is required; it automatically probes the same default socket under `CODEX_HOME`. + Codexometer offers Codex's `zstd-stream-v1` WebSocket compression on that + connection. Compression is used only when the server acknowledges it; + older servers and servers with compression disabled continue using JSON + text. Encoder/decoder history is reset on every connection, with bounded + messages and strict compressed-message framing. No server setting is changed. + If the latest Codexometer build is already running, leave it open: it retries the connection and discovers newly connected sessions automatically. Allow a refresh cycle. Restart it if you have replaced its binary or changed its @@ -763,7 +769,8 @@ new evidence accumulates. Future improvement: consume the actual response prefer that evidence to requested settings. Codexometer prefers Codex's durable **`token_usage_record`** entries for each -newly completed response. Exact records are counted independently of cumulative +reported response, including usage that Codex persists for failed/incomplete +requests. Exact records are counted independently of cumulative context counters, with bounded response-ID deduplication; inherited parent records are excluded from a child's own usage. Startup establishes a historical baseline without charging past responses, using a bounded 4 MiB tail scan. @@ -773,6 +780,38 @@ another billable response. Older rollouts retain the legacy token-count reader. an existing thread establishes a new baseline rather than charging copied history again; session settings come from its newest observed rollout. +Compressed `.jsonl.zst` histories are streamed into bounded in-memory baselines; +no decompressed transcript copy is written to disk. Their decoded offsets and +recent response identities are retained. When Codex restores a plain rollout, +Codexometer verifies the entire historical prefix before consuming appended +responses. Rewritten histories establish a fresh baseline. Source and decoded +histories are each limited to 256 MiB per compressed file, with bounded decoder +windows and record buffers; unreadable or oversized histories report incomplete +coverage instead of silently qualifying quota-learning intervals. + +Codex 0.164.0's experimental background compaction can consume tokens without +publishing the corresponding exact foreground response record. Codexometer +checks the loaded thread's effective `async_compaction` and `token_budget` +feature state through a read-only inventory. A potentially unobserved background +path pauses API-EQ quota learning, clears prior capacity estimates, and displays +an **INCOMPLETE COVERAGE** diagnostic in terminal/browser Quota. Child sessions +are checked separately. Known risks survive a daemon disconnect; a verified +recovery starts a fresh learning interval. Observed token/cost totals remain +available and no missing response cost is invented. + +Without effective daemon feature state, relevant local configuration is checked +read-only. Enabled flags, including flags in potentially selected profiles, are +conservative coverage warnings rather than proof of the current runtime mode. +This fallback cannot establish hidden CLI overrides, remote configuration or +unobserved usage from another machine. Those existing observation limits remain; +Codexometer does not change Codex's compaction configuration. + +Model, speed and permission changes are confirmed against the running thread +rather than treating a queued settings acknowledgement as success. Verification +uses resume without overrides, stays bound to the original connection, and does +not retry mutations or block approval/event processing. Account changes refresh +the model/options catalogue while retaining the observed conversation. + The requested model remains the fallback when actual per-response model evidence is unavailable; durable usage records do not supply model or billed-tier fields. Diagnostics explicitly distinguish requested-model attribution from a matching @@ -3450,7 +3489,7 @@ virtual clock rather than depending on millisecond wall-clock scheduling. Codexometer uses: -- Go 1.27.2+ +- Go 1.27.3+ - Bubble Tea v2 for the terminal event loop and declarative terminal modes - Lip Gloss v2 for adaptive ANSI styling and layout - Starlark for deterministic, hermetic benchmark-code evaluation diff --git a/go.mod b/go.mod index 855572a..a8ee175 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/merefield/codexometer -go 1.27.2 +go 1.27.3 require ( charm.land/bubbles/v2 v2.2.1 @@ -11,6 +11,8 @@ require ( github.com/charmbracelet/x/ansi v0.11.8 github.com/charmbracelet/x/term v0.2.2 github.com/gorilla/websocket v1.5.3 + github.com/klauspost/compress v1.20.3 + github.com/pelletier/go-toml/v2 v2.4.3 go.starlark.net v0.0.0-20260930220527-d7438c5a85ac golang.org/x/sync v0.23.0 golang.org/x/sys v0.48.0 diff --git a/go.sum b/go.sum index 057603a..6bd8508 100644 --- a/go.sum +++ b/go.sum @@ -32,12 +32,16 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/klauspost/compress v1.20.3 h1:zWNoFqq/lSuYQRim3dUXlXeA0IqwuZGicdG5ZTfY5j4= +github.com/klauspost/compress v1.20.3/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss= github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0= github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= diff --git a/internal/codex/daemon_native_unix_test.go b/internal/codex/daemon_native_unix_test.go new file mode 100644 index 0000000..6f8972a --- /dev/null +++ b/internal/codex/daemon_native_unix_test.go @@ -0,0 +1,87 @@ +//go:build unix + +package codex + +import ( + "context" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" +) + +// Opt-in integration check: no credentials, user configuration, model requests, +// thread creation or shared daemon are used. CI otherwise uses wire fixtures. +func TestNativeCodex164Compression(t *testing.T) { + if os.Getenv("CODEXOMETER_TEST_CODEX_164") != "1" { + t.Skip("set CODEXOMETER_TEST_CODEX_164=1 with Codex 0.164.0 installed") + } + binary, err := exec.LookPath("codex") + if err != nil { + t.Fatal(err) + } + version, err := exec.Command(binary, "--version").Output() + if err != nil || strings.TrimSpace(string(version)) != "codex-cli 0.164.0" { + t.Fatal("expected Codex 0.164.0", string(version), err) + } + home, err := os.MkdirTemp("/tmp", "cxm-native-") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { os.RemoveAll(home) }) + ctx, cancel := context.WithCancel(context.Background()) + command := exec.CommandContext(ctx, binary, "-c", "features.app_server_websocket_compression=true", "app-server", "--listen", "unix://") + command.Dir = home + command.Env = environmentWith(environmentWithout(os.Environ(), "CODEX_HOME", "OPENAI_API_KEY", "CODEX_API_KEY"), "CODEX_HOME", home) + log, err := os.Create(filepath.Join(home, "server.log")) + if err != nil { + t.Fatal(err) + } + command.Stderr = log + command.Stdout = log + if err := command.Start(); err != nil { + cancel() + log.Close() + t.Fatal(err) + } + t.Cleanup(func() { cancel(); command.Wait(); log.Close() }) + p := newSessionStatusProvider(home).(*daemonStatusProvider) + t.Cleanup(func() { p.disconnect(nil) }) + deadline := time.Now().Add(10 * time.Second) + for { + err = p.ensureConnected(context.Background()) + if err == nil { + break + } + if time.Now().After(deadline) { + t.Fatal("native handshake failed", err) + } + time.Sleep(20 * time.Millisecond) + } + p.mu.Lock() + compressed := p.wire != nil && p.wire.decoder != nil + p.mu.Unlock() + if !compressed { + t.Fatal("Codex did not acknowledge zstd-stream-v1") + } + for range 5 { + var result struct { + Data []struct { + Name string + Enabled bool + } + } + if err := p.request(context.Background(), "experimentalFeature/list", map[string]any{}, &result); err != nil { + t.Fatal("native compressed RPC", err) + } + if len(result.Data) == 0 { + t.Fatal("native feature inventory empty") + } + } + loaded, err := p.loadedThreads(context.Background()) + if err != nil || len(loaded) != 0 { + t.Fatal("isolated server unexpectedly has loaded threads", loaded, err) + } +} diff --git a/internal/codex/daemon_status.go b/internal/codex/daemon_status.go index bbd54e7..df3dbb6 100644 --- a/internal/codex/daemon_status.go +++ b/internal/codex/daemon_status.go @@ -18,6 +18,7 @@ type sessionStatusProvider interface { } type sessionDaemonSnapshot struct { + UsageCoverage map[string]string Contexts map[string]SessionContext WorkingDirectories map[string]string Statuses map[string]sessionRuntimeStatus diff --git a/internal/codex/daemon_status_unix.go b/internal/codex/daemon_status_unix.go index 7eba0a4..6e9e54f 100644 --- a/internal/codex/daemon_status_unix.go +++ b/internal/codex/daemon_status_unix.go @@ -31,6 +31,7 @@ type daemonStatusProvider struct { mu sync.Mutex connection *websocket.Conn + wire *daemonWire nextRequestID int64 pending map[int64]chan daemonEnvelope subscribed map[string]struct{} @@ -40,6 +41,7 @@ type daemonStatusProvider struct { lastStatusAt time.Time statusThreads map[string]struct{} statuses map[string]sessionRuntimeStatus + usageCoverage map[string]string threadSettings map[string]QuotaSession threadDirectories map[string]string settingsVersions map[string]uint64 @@ -98,6 +100,7 @@ func (p *daemonStatusProvider) Fetch(ctx context.Context, threadIDs []string) (s } } + coverage := p.readUsageCoverage(ctx, threadIDs) statuses := make(map[string]sessionRuntimeStatus, len(threadIDs)) for _, threadID := range threadIDs { var result struct { @@ -121,6 +124,7 @@ func (p *daemonStatusProvider) Fetch(ctx context.Context, threadIDs []string) (s } p.mu.Lock() + p.usageCoverage = coverage p.lastStatusAt = time.Now() p.statusThreads = stringStructSet(threadIDs) for id, status := range statuses { @@ -161,7 +165,12 @@ func (p *daemonStatusProvider) statusSnapshotLocked(threadIDs []string) sessionD for threadID := range p.subscribed { subscribed[threadID] = struct{}{} } + coverage := make(map[string]string, len(p.usageCoverage)) + for id, issue := range p.usageCoverage { + coverage[id] = issue + } return sessionDaemonSnapshot{ + UsageCoverage: coverage, Contexts: daemonContextSnapshot(p.contexts, threadIDs, statuses), WorkingDirectories: directories, Statuses: statuses, @@ -243,7 +252,7 @@ func (p *daemonStatusProvider) ensureConnected(ctx context.Context) error { return (&net.Dialer{Timeout: daemonStatusTimeout}).DialContext(ctx, "unix", p.socketPath) }, } - connection, response, err := dialer.DialContext(ctx, "ws://localhost/", http.Header{}) + connection, response, err := dialer.DialContext(ctx, "ws://localhost/", http.Header{http.CanonicalHeaderKey(daemonCompressionHeader): []string{daemonCompressionProtocol}}) if response != nil && response.Body != nil { _ = response.Body.Close() } @@ -251,13 +260,19 @@ func (p *daemonStatusProvider) ensureConnected(ctx context.Context) error { return err } + wire, err := newDaemonWire(connection, response.Header) + if err != nil { + _ = connection.Close() + return err + } p.mu.Lock() if p.connection != nil { p.mu.Unlock() - _ = connection.Close() + wire.close() return nil } p.connection = connection + p.wire = wire p.commandGeneration++ p.commandTransitions = make(map[string]uint64) p.pending = make(map[int64]chan daemonEnvelope) @@ -268,7 +283,7 @@ func (p *daemonStatusProvider) ensureConnected(ctx context.Context) error { p.settingsVersions = make(map[string]uint64) p.settingsSignal = make(chan struct{}) p.mu.Unlock() - go p.readLoop(connection) + go p.readLoop(connection, wire) var initialized struct { UserAgent string `json:"userAgent"` @@ -385,6 +400,13 @@ func (p *daemonStatusProvider) write(connection *websocket.Conn, value any) erro p.writeMu.Lock() defer p.writeMu.Unlock() _ = connection.SetWriteDeadline(time.Now().Add(daemonStatusTimeout)) + p.mu.Lock() + wire := p.wire + current := p.connection + p.mu.Unlock() + if wire != nil && current == connection { + return wire.writeJSON(value) + } return connection.WriteJSON(value) } @@ -394,16 +416,24 @@ func (p *daemonStatusProvider) dropPending(id int64) { p.mu.Unlock() } -func (p *daemonStatusProvider) readLoop(connection *websocket.Conn) { +func (p *daemonStatusProvider) readLoop(connection *websocket.Conn, wires ...*daemonWire) { + wire := &daemonWire{conn: connection} + if len(wires) > 0 { + wire = wires[0] + } for { var envelope daemonEnvelope - if err := connection.ReadJSON(&envelope); err != nil { + if err := wire.readJSON(&envelope); err != nil { p.disconnect(connection) return } var responseID int64 if envelope.Method == "" && len(envelope.ID) > 0 && json.Unmarshal(envelope.ID, &responseID) == nil { p.mu.Lock() + if p.connection != connection { + p.mu.Unlock() + return + } response := p.pending[responseID] delete(p.pending, responseID) p.mu.Unlock() @@ -416,6 +446,10 @@ func (p *daemonStatusProvider) readLoop(connection *websocket.Conn) { continue } p.mu.Lock() + if p.connection != connection { + p.mu.Unlock() + return + } if envelope.Method == "thread/queue/changed" { p.queueRevision++ } @@ -441,6 +475,11 @@ func (p *daemonStatusProvider) handleNotification(method string, params json.Raw } } switch method { + case "account/updated": + p.mu.Lock() + p.commandGeneration++ + p.lastStatusAt = time.Time{} + p.mu.Unlock() case "thread/settings/updated": var notification struct { ThreadID string `json:"threadId"` @@ -541,6 +580,8 @@ func (p *daemonStatusProvider) disconnect(connection *websocket.Conn) { return } current := p.connection + wire := p.wire + p.wire = nil p.connection = nil for id, response := range p.pending { delete(p.pending, id) @@ -557,13 +598,16 @@ func (p *daemonStatusProvider) disconnect(connection *websocket.Conn) { p.statuses = nil p.commandTransitions = nil p.threadSettings = nil + p.usageCoverage = nil p.threadDirectories = nil if p.settingsSignal != nil { close(p.settingsSignal) p.settingsSignal = nil } p.mu.Unlock() - if current != nil { + if wire != nil { + wire.close() + } else if current != nil { _ = current.Close() } } diff --git a/internal/codex/daemon_wire.go b/internal/codex/daemon_wire.go new file mode 100644 index 0000000..21478d8 --- /dev/null +++ b/internal/codex/daemon_wire.go @@ -0,0 +1,260 @@ +package codex + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "sync" + "unicode/utf8" + + "github.com/gorilla/websocket" + "github.com/klauspost/compress/zstd" +) + +const daemonCompressionHeader = "x-codex-app-server-compression" +const daemonCompressionProtocol = "zstd-stream-v1" +const daemonMessageLimit = 16 << 20 +const daemonZstdWindow = 1 << 20 + +// This is Codex's application-level, flushed zstd stream protocol, not the +// WebSocket permessage-deflate extension. Streams belong to one connection. +type daemonWire struct { + conn *websocket.Conn + mu sync.Mutex + encoder *zstd.Encoder + encoded bytes.Buffer + decoder *daemonStreamDecoder +} + +func newDaemonWire(conn *websocket.Conn, headers http.Header) (*daemonWire, error) { + w := &daemonWire{conn: conn} + conn.SetReadLimit(daemonMessageLimit + (1 << 20)) + values := headers.Values(daemonCompressionHeader) + if len(values) == 0 { + return w, nil + } + if len(values) != 1 || values[0] != daemonCompressionProtocol { + return nil, errors.New("unsupported daemon WebSocket compression acknowledgement") + } + encoder, err := zstd.NewWriter(&w.encoded, zstd.WithEncoderConcurrency(1), zstd.WithWindowSize(daemonZstdWindow), zstd.WithEncoderLevel(zstd.SpeedFastest)) + if err != nil { + return nil, err + } + w.encoder = encoder + w.decoder = newDaemonStreamDecoder() + return w, nil +} + +func (w *daemonWire) writeJSON(value any) error { + data, err := json.Marshal(value) + if err != nil { + return err + } + if len(data) > daemonMessageLimit { + return errors.New("daemon message exceeds size limit") + } + w.mu.Lock() + defer w.mu.Unlock() + if w.encoder == nil { + return w.conn.WriteMessage(websocket.TextMessage, data) + } + w.encoded.Reset() + if _, err = w.encoder.Write(data); err != nil { + return err + } + if err = w.encoder.Flush(); err != nil { + return err + } + return w.conn.WriteMessage(websocket.BinaryMessage, w.encoded.Bytes()) +} + +func (w *daemonWire) readJSON(value any) error { + kind, data, err := w.conn.ReadMessage() + if err != nil { + return err + } + if w.decoder != nil { + if kind != websocket.BinaryMessage { + return errors.New("compressed daemon sent a non-binary message") + } + data, err = w.decoder.decode(data) + if err != nil { + return err + } + } else if kind != websocket.TextMessage { + return errors.New("unnegotiated binary daemon message") + } + if len(data) > daemonMessageLimit || !utf8.Valid(data) { + return errors.New("invalid or oversized daemon JSON message") + } + return json.Unmarshal(data, value) +} + +func (w *daemonWire) close() { + // Closing the socket interrupts its reader/writer before waiting for codec IO. + _ = w.conn.Close() + w.mu.Lock() + defer w.mu.Unlock() + if w.encoder != nil { + _ = w.encoder.Close() + } + if w.decoder != nil { + w.decoder.close() + } +} + +type daemonDecodedChunk struct { + data []byte + err error +} + +// The synchronous decoder writes each decoded block before requesting the +// next compressed block. At a validated flush boundary the reader hands back +// the collected message, then waits for the next WebSocket message without +// returning EOF (which would discard stream history). One worker per connection +// owns all decoder state and exits when the connection closes. +type daemonStreamDecoder struct { + chunks chan []byte + results chan daemonDecodedChunk + stop chan struct{} + done chan struct{} + once sync.Once + inFrame bool + checksum bool +} + +type daemonChunkReader struct { + stream *daemonStreamDecoder + input []byte + active bool + output bytes.Buffer +} + +func newDaemonStreamDecoder() *daemonStreamDecoder { + d := &daemonStreamDecoder{chunks: make(chan []byte), results: make(chan daemonDecodedChunk), stop: make(chan struct{}), done: make(chan struct{})} + go func() { + defer close(d.done) + reader := &daemonChunkReader{stream: d} + decoder, err := zstd.NewReader(reader, zstd.WithDecoderConcurrency(1), zstd.WithDecoderMaxWindow(daemonZstdWindow), zstd.WithDecoderMaxMemory(8<<20)) + if err == nil { + _, err = decoder.WriteTo(reader) + decoder.Close() + } + if err == nil { + err = io.ErrUnexpectedEOF + } + select { + case d.results <- daemonDecodedChunk{err: err}: + case <-d.stop: + } + }() + return d +} + +func (r *daemonChunkReader) Read(p []byte) (int, error) { + if len(r.input) == 0 { + if r.active { + result := daemonDecodedChunk{data: bytes.Clone(r.output.Bytes())} + r.output.Reset() + r.active = false + select { + case r.stream.results <- result: + case <-r.stream.stop: + return 0, io.EOF + } + } + select { + case r.input = <-r.stream.chunks: + r.active = true + case <-r.stream.stop: + return 0, io.EOF + } + } + n := copy(p, r.input) + r.input = r.input[n:] + return n, nil +} + +func (r *daemonChunkReader) Write(p []byte) (int, error) { + if len(p) > daemonMessageLimit-r.output.Len() { + return 0, errors.New("decompressed daemon message exceeds size limit") + } + return r.output.Write(p) +} + +func (d *daemonStreamDecoder) decode(data []byte) ([]byte, error) { + if err := d.validateFlush(data); err != nil { + return nil, err + } + select { + case d.chunks <- data: + case <-d.done: + return nil, errors.New("daemon decompressor closed") + case <-d.stop: + return nil, io.EOF + } + select { + case result := <-d.results: + return result.data, result.err + case <-d.done: + return nil, errors.New("daemon decompressor failed") + case <-d.stop: + return nil, io.EOF + } +} + +func (d *daemonStreamDecoder) close() { d.once.Do(func() { close(d.stop) }); <-d.done } + +// A partial block must never be joined to the next RPC message. Validate block +// framing first; the library then validates actual compressed data and checksums. +func (d *daemonStreamDecoder) validateFlush(data []byte) error { + if len(data) == 0 || len(data) > daemonMessageLimit+(1<<20) { + return errors.New("invalid compressed daemon message size") + } + inFrame, checksum := d.inFrame, d.checksum + for len(data) > 0 { + if !inFrame { + var header zstd.Header + if err := header.Decode(data); err != nil { + return fmt.Errorf("daemon zstd header: %w", err) + } + if header.Skippable || header.DictionaryID != 0 || header.WindowSize > daemonZstdWindow || header.SingleSegment && header.FrameContentSize > daemonZstdWindow { + return errors.New("unsupported daemon zstd frame") + } + data = data[header.HeaderSize:] + inFrame = true + checksum = header.HasCheckSum + } + if len(data) < 3 { + return errors.New("truncated daemon zstd block header") + } + block := uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16 + size := int(block >> 3) + kind := (block >> 1) & 3 + last := block&1 != 0 + if kind == 3 || size > 128<<10 { + return errors.New("invalid daemon zstd block") + } + if kind == 1 { + size = 1 + } + if len(data) < 3+size { + return errors.New("truncated daemon zstd block") + } + data = data[3+size:] + if last { + if checksum { + if len(data) < 4 { + return errors.New("truncated daemon zstd checksum") + } + data = data[4:] + } + inFrame = false + } + } + d.inFrame, d.checksum = inFrame, checksum + return nil +} diff --git a/internal/codex/daemon_wire_test.go b/internal/codex/daemon_wire_test.go new file mode 100644 index 0000000..3a23971 --- /dev/null +++ b/internal/codex/daemon_wire_test.go @@ -0,0 +1,196 @@ +package codex + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + "time" + + "github.com/gorilla/websocket" + "github.com/klauspost/compress/zstd" +) + +func TestDaemonDecoderNativeZstdFlushes(t *testing.T) { + // Generated with native libzstd ZSTD_compressStream2(ZSTD_e_flush), the same + // stream framing used by Codex's Rust zstd crate. Later messages reuse history. + var fixtures []struct{ JSON, Hex string } + data, err := os.ReadFile("testdata/zstd-stream-v1.json") + if err != nil { + t.Fatal(err) + } + if err = json.Unmarshal(data, &fixtures); err != nil { + t.Fatal(err) + } + d := newDaemonStreamDecoder() + defer d.close() + for _, fixture := range fixtures { + input, err := hex.DecodeString(fixture.Hex) + if err != nil { + t.Fatal(err) + } + got, err := d.decode(input) + if err != nil || string(got) != fixture.JSON { + t.Fatal("native stream failed", string(got), err) + } + } + // Reconnect must discard the previous connection's dictionary/history. + fresh := newDaemonStreamDecoder() + defer fresh.close() + second, _ := hex.DecodeString(fixtures[1].Hex) + if _, err := fresh.decode(second); err == nil { + t.Fatal("continuation accepted without first frame") + } +} + +func TestDaemonWireNegotiationAndStreaming(t *testing.T) { + for _, compressed := range []bool{false, true} { + t.Run(map[bool]string{false: "legacy", true: "zstd"}[compressed], func(t *testing.T) { + serverErr := make(chan error, 1) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + header := http.Header{} + if compressed { + header.Set(daemonCompressionHeader, daemonCompressionProtocol) + } + conn, err := (&websocket.Upgrader{}).Upgrade(w, r, header) + if err != nil { + serverErr <- err + return + } + wire, err := newDaemonWire(conn, header) + if err != nil { + serverErr <- err + return + } + defer wire.close() + for i := 0; i < 8; i++ { + var request map[string]any + if err = wire.readJSON(&request); err != nil { + serverErr <- err + return + } + if err = wire.writeJSON(request); err != nil { + serverErr <- err + return + } + } + serverErr <- nil + })) + defer server.Close() + conn, response, err := websocket.DefaultDialer.Dial("ws"+strings.TrimPrefix(server.URL, "http"), http.Header{http.CanonicalHeaderKey(daemonCompressionHeader): []string{daemonCompressionProtocol}}) + if err != nil { + t.Fatal(err) + } + wire, err := newDaemonWire(conn, response.Header) + if err != nil { + t.Fatal(err) + } + defer wire.close() + for i := 0; i < 8; i++ { + text := strings.Repeat("retained stream history ", 15000) + if err = wire.writeJSON(map[string]any{"id": i, "text": text}); err != nil { + t.Fatal(err) + } + var result struct { + ID int + Text string + } + if err = wire.readJSON(&result); err != nil || result.ID != i || result.Text != text { + t.Fatal("round trip", result.ID, err) + } + } + if err := <-serverErr; err != nil { + t.Fatal(err) + } + }) + } +} + +func TestDaemonDecoderRejectsBrokenAndOversizedFrames(t *testing.T) { + var encoded bytes.Buffer + encoder, err := zstd.NewWriter(&encoded, zstd.WithEncoderConcurrency(1), zstd.WithWindowSize(daemonZstdWindow)) + if err != nil { + t.Fatal(err) + } + encoder.Write([]byte(`{"result":"ok"}`)) + encoder.Flush() + valid := bytes.Clone(encoded.Bytes()) + encoder.Close() + for _, bad := range [][]byte{nil, []byte("invalid zstd"), valid[:len(valid)-1], append(bytes.Clone(valid), 1)} { + d := newDaemonStreamDecoder() + if _, err := d.decode(bad); err == nil { + t.Error("broken frame accepted") + } + d.close() + } + encoded.Reset() + encoder, _ = zstd.NewWriter(&encoded, zstd.WithEncoderConcurrency(1), zstd.WithWindowSize(daemonZstdWindow)) + encoder.Write(bytes.Repeat([]byte{'x'}, daemonMessageLimit+1)) + encoder.Flush() + d := newDaemonStreamDecoder() + if _, err := d.decode(encoded.Bytes()); err == nil { + t.Fatal("decompression limit ignored") + } + d.close() + encoder.Close() +} + +func TestDaemonDecoderCloseUnblocksIdleStream(t *testing.T) { + d := newDaemonStreamDecoder() + done := make(chan struct{}) + go func() { d.close(); close(done) }() + select { + case <-done: + case <-time.After(time.Second): + t.Fatal("idle decoder leaked") + } + d.close() +} + +func TestDaemonWireRejectsUnknownAcknowledgement(t *testing.T) { + conn := &websocket.Conn{} + for _, values := range [][]string{{"future"}, {daemonCompressionProtocol, daemonCompressionProtocol}} { + if _, err := newDaemonWire(conn, http.Header{http.CanonicalHeaderKey(daemonCompressionHeader): values}); err == nil { + t.Fatal("unknown negotiation accepted") + } + } +} + +func TestDaemonWireRejectsWrongMessageType(t *testing.T) { + for _, compressed := range []bool{false, true} { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + headers := http.Header{} + if compressed { + headers.Set(daemonCompressionHeader, daemonCompressionProtocol) + } + conn, err := (&websocket.Upgrader{}).Upgrade(w, r, headers) + if err != nil { + return + } + defer conn.Close() + kind := websocket.BinaryMessage + if compressed { + kind = websocket.TextMessage + } + _ = conn.WriteMessage(kind, []byte(`{"result":{}}`)) + })) + conn, response, err := websocket.DefaultDialer.Dial("ws"+strings.TrimPrefix(server.URL, "http"), nil) + if err != nil { + t.Fatal(err) + } + wire, err := newDaemonWire(conn, response.Header) + if err != nil { + t.Fatal(err) + } + var value any + if err := wire.readJSON(&value); err == nil { + t.Error("message type accepted without matching negotiation") + } + wire.close() + server.Close() + } +} diff --git a/internal/codex/live_usage.go b/internal/codex/live_usage.go index b58a9c6..a72e3e9 100644 --- a/internal/codex/live_usage.go +++ b/internal/codex/live_usage.go @@ -52,6 +52,7 @@ type LiveUsageSnapshot struct { APIEqTierPremiumUSD float64 APIEqUnknownTierCalls int64 APIEqDiagnostics []APIEqDiagnostic + APIEqCoverageIssue string } // LiveUsageSession is one independently started local Codex session. Token @@ -126,15 +127,18 @@ type LiveTurnTiming struct { // sessions. It also extracts bounded display-only replies and request context; // reasoning and arbitrary tool output are never retained. type LiveUsageReader struct { - sessionNames map[string]string - nameIndexInfo os.FileInfo - daemonContexts map[string]SessionContext - daemonDirectories map[string]string - SessionsRoot string - WriterLocksRoot string - statusProvider sessionStatusProvider - commandMu sync.Mutex - commandMenus *sessionCommandCache + knownUsageCoverage map[string]string + localFeatureChecks map[string]localFeatureCheck + discoveryCoverageIssue string + sessionNames map[string]string + nameIndexInfo os.FileInfo + daemonContexts map[string]SessionContext + daemonDirectories map[string]string + SessionsRoot string + WriterLocksRoot string + statusProvider sessionStatusProvider + commandMu sync.Mutex + commandMenus *sessionCommandCache mu sync.Mutex initialized bool @@ -181,6 +185,10 @@ type rolloutCursor struct { modelCalls []LiveModelCall turnTimings []LiveTurnTiming attention sessionAttentionState + compressed bool + compressedInfo os.FileInfo + compressedTail []byte + compressedDigest [32]byte exactUsage bool exactUsageUncertain bool exactResponses map[string]BenchmarkUsage @@ -378,6 +386,7 @@ func (r *LiveUsageReader) fetchTokenUsage(ctx context.Context, forceFullDiscover } exactStatuses := map[string]sessionRuntimeStatus(nil) + var daemonCoverage map[string]string r.daemonContexts = nil r.daemonDirectories = nil appServerUp := false @@ -387,6 +396,7 @@ func (r *LiveUsageReader) fetchTokenUsage(ctx context.Context, forceFullDiscover appServerUp = true exactStatuses = daemonSnapshot.Statuses r.daemonContexts = daemonSnapshot.Contexts + daemonCoverage = daemonSnapshot.UsageCoverage r.daemonDirectories = daemonSnapshot.WorkingDirectories r.ingestResolvedModelObservations(daemonSnapshot.ModelObservations) r.daemonSubscribedThreads = daemonSnapshot.SubscribedThreads @@ -400,11 +410,36 @@ func (r *LiveUsageReader) fetchTokenUsage(ctx context.Context, forceFullDiscover if err := contextErr(ctx); err != nil { return LiveUsageSnapshot{}, err } + if cursor.compressed { + info, err := os.Stat(path) + if errors.Is(err, os.ErrNotExist) { + continue + } + if err == nil { + err = r.restoreCompressedCursor(path, info, cursor) + } + if err != nil { + if firstReadErr == nil { + firstReadErr = err + } + continue + } + cursor = r.files[path] + } if now.Sub(cursor.lastModified) > activeRolloutHorizon { continue } if err := r.consume(path, cursor); err != nil { if errors.Is(err, os.ErrNotExist) { + if info, compressedErr := os.Stat(path + ".zst"); compressedErr == nil { + if compressedErr = r.addCompressedFile(ctx, path+".zst", info); compressedErr != nil { + r.discoveryCoverageIssue = "compressed rollout history could not be verified" + if firstReadErr == nil { + firstReadErr = compressedErr + } + } + continue + } delete(r.files, path) continue } @@ -425,6 +460,10 @@ func (r *LiveUsageReader) fetchTokenUsage(ctx context.Context, forceFullDiscover for i := range sessions { sessions[i].Name = r.sessionNames[sessions[i].ID] } + coverageIssue := r.usageCoverage(ctx, sessions, daemonCoverage) + if firstReadErr != nil { + coverageIssue = "local response telemetry could not be completely read" + } codexStatusKnown, codexUp, codexWorking := codexRuntimeHealth( appServerUp, len(liveWriters) > 0, sessionWorking, writerLocksSupported, ) @@ -443,6 +482,7 @@ func (r *LiveUsageReader) fetchTokenUsage(ctx context.Context, forceFullDiscover APIEqTierPremiumUSD: r.apiEqTierPremiumUSD, APIEqUnknownTierCalls: r.apiEqUnknownTierCalls, APIEqDiagnostics: append([]APIEqDiagnostic(nil), r.apiEqDiagnostics...), + APIEqCoverageIssue: coverageIssue, }, nil } @@ -485,6 +525,9 @@ func (r *LiveUsageReader) initialize(ctx context.Context, now time.Time) error { } func (r *LiveUsageReader) discover(ctx context.Context, now time.Time, full bool) error { + if full { + r.discoveryCoverageIssue = "" + } roots := []string{r.SessionsRoot} if !full { roots = roots[:0] @@ -511,7 +554,7 @@ func (r *LiveUsageReader) discover(ctx context.Context, now time.Time, full bool if err := contextErr(ctx); err != nil { return err } - if entry.IsDir() || entry.Type()&os.ModeSymlink != 0 || !isRolloutFile(entry.Name()) { + if entry.IsDir() || entry.Type()&os.ModeSymlink != 0 || (!isRolloutFile(entry.Name()) && !isCompressedRolloutFile(entry.Name())) { return nil } info, err := entry.Info() @@ -521,7 +564,19 @@ func (r *LiveUsageReader) discover(ctx context.Context, now time.Time, full bool } return nil } + if isCompressedRolloutFile(entry.Name()) { + if err := r.addCompressedFile(ctx, path, info); err != nil { + r.discoveryCoverageIssue = "compressed rollout history could not be verified" + if firstFileErr == nil { + firstFileErr = err + } + } + return nil + } if cursor, exists := r.files[path]; exists { + if cursor.compressed { + return r.restoreCompressedCursor(path, info, cursor) + } cursor.lastModified = info.ModTime() return nil } @@ -604,6 +659,11 @@ func (r *LiveUsageReader) addFile(path string, info os.FileInfo) error { r.apiEqAccountingAdjustments++ } } + // Replacing this path changes the historical baseline, not the activity + // already observed by this process. Keep it only for the same thread. + if previous := r.files[path]; previous != nil && cursor.threadID != "" && previous.threadID == cursor.threadID { + cursor.observedTokens, cursor.modelCalls, cursor.turnTimings = previous.observedTokens, previous.modelCalls, previous.turnTimings + } r.files[path] = cursor return nil } @@ -1236,27 +1296,8 @@ func readRolloutMetadata(path string) (rolloutMetadata, error) { reader := bufio.NewReader(file) for record := 0; record < 64; record++ { line, readErr := reader.ReadBytes('\n') - if bytes.Contains(line, []byte(`"session_meta"`)) { - var event rolloutMetadataEvent - if json.Unmarshal(bytes.TrimSpace(line), &event) == nil && event.Type == "session_meta" { - var payload rolloutMetadataPayload - if json.Unmarshal(event.Payload, &payload) == nil { - parent, nonRoot := sessionSourceParent(payload.Source) - if payload.ParentThreadID != "" { - parent = payload.ParentThreadID - nonRoot = true - } - if payload.ThreadSource == "subagent" { - nonRoot = true - } - return rolloutMetadata{ - ID: payload.ID, ParentThreadID: parent, - WorkingDirectory: payload.WorkingDirectory, NonRoot: nonRoot, - StartedAt: firstNonZeroTime(payload.Timestamp, event.Timestamp), - SubagentHistoryStartOrdinal: payload.SubagentHistoryStartOrdinal, - }, nil - } - } + if metadata, ok := rolloutMetadataRecord(line); ok { + return metadata, nil } if readErr != nil { if errors.Is(readErr, io.EOF) { @@ -1268,6 +1309,32 @@ func readRolloutMetadata(path string) (rolloutMetadata, error) { return rolloutMetadata{}, nil } +func rolloutMetadataRecord(line []byte) (rolloutMetadata, bool) { + if bytes.Contains(line, []byte(`"session_meta"`)) { + var event rolloutMetadataEvent + if json.Unmarshal(bytes.TrimSpace(line), &event) == nil && event.Type == "session_meta" { + var payload rolloutMetadataPayload + if json.Unmarshal(event.Payload, &payload) == nil { + parent, nonRoot := sessionSourceParent(payload.Source) + if payload.ParentThreadID != "" { + parent = payload.ParentThreadID + nonRoot = true + } + if payload.ThreadSource == "subagent" { + nonRoot = true + } + return rolloutMetadata{ + ID: payload.ID, ParentThreadID: parent, + WorkingDirectory: payload.WorkingDirectory, NonRoot: nonRoot, + StartedAt: firstNonZeroTime(payload.Timestamp, event.Timestamp), + SubagentHistoryStartOrdinal: payload.SubagentHistoryStartOrdinal, + }, true + } + } + } + return rolloutMetadata{}, false +} + func sessionSourceParent(raw json.RawMessage) (string, bool) { if len(raw) == 0 || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { return "", false diff --git a/internal/codex/live_usage_compressed.go b/internal/codex/live_usage_compressed.go new file mode 100644 index 0000000..07740d0 --- /dev/null +++ b/internal/codex/live_usage_compressed.go @@ -0,0 +1,190 @@ +package codex + +import ( + "bufio" + "bytes" + "context" + "crypto/sha256" + "errors" + "fmt" + "io" + "os" + "strings" + + "github.com/klauspost/compress/zstd" +) + +const compressedHistoryLimit = 256 << 20 +const rolloutBaselineLineLimit = 4 << 20 + +func isCompressedRolloutFile(name string) bool { + return strings.HasSuffix(name, ".zst") && isRolloutFile(strings.TrimSuffix(name, ".zst")) +} + +// Compressed histories are immutable baselines, never new spending. Keep their +// logical .jsonl identity and decoded offset so restoring them preserves both +// observed totals and the boundary between history and concurrent appends. +// No transcript copy is written to disk and retained memory is bounded. +func (r *LiveUsageReader) addCompressedFile(ctx context.Context, path string, info os.FileInfo) error { + plain := strings.TrimSuffix(path, ".zst") + if _, err := os.Stat(plain); err == nil { + return nil + } else if !errors.Is(err, os.ErrNotExist) { + return err + } + old := r.files[plain] + if old != nil && old.compressed && old.compressedInfo.Size() == info.Size() && old.compressedInfo.ModTime() == info.ModTime() { + return nil + } + cursor, err := compressedRolloutBaseline(ctx, path, info) + if err != nil { + return fmt.Errorf("read compressed rollout: %w", err) + } + if old != nil && old.threadID == cursor.threadID { + cursor.observedTokens, cursor.modelCalls, cursor.turnTimings = old.observedTokens, old.modelCalls, old.turnTimings + if old.offset != cursor.offset { + r.apiEqAccountingAdjustments++ + } + } + r.files[plain] = cursor + return nil +} + +func compressedRolloutBaseline(ctx context.Context, path string, info os.FileInfo) (*rolloutCursor, error) { + if info.Size() > compressedHistoryLimit { + return nil, errors.New("compressed history exceeds 256 MiB source limit") + } + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + decoder, err := zstd.NewReader(io.LimitReader(file, compressedHistoryLimit+1), zstd.WithDecoderConcurrency(1), zstd.WithDecoderMaxWindow(128<<20), zstd.WithDecoderMaxMemory(128<<20)) + if err != nil { + return nil, err + } + defer decoder.Close() + reader := bufio.NewReaderSize(io.LimitReader(decoder, compressedHistoryLimit+1), 64<<10) + cursor := &rolloutCursor{lastModified: info.ModTime(), compressed: true, compressedInfo: info} + var line, tail []byte + digest := sha256.New() + var decoded int64 + oversized := false + for { + if err := ctx.Err(); err != nil { + return nil, err + } + chunk, readErr := reader.ReadSlice('\n') + decoded += int64(len(chunk)) + digest.Write(chunk) + if decoded > compressedHistoryLimit { + return nil, errors.New("decoded history exceeds 256 MiB baseline limit") + } + tail = append(tail, chunk...) + if len(tail) > 4096 { + tail = append(tail[:0], tail[len(tail)-4096:]...) + } + if len(line)+len(chunk) <= rolloutBaselineLineLimit && !oversized { + line = append(line, chunk...) + } else { + oversized = true + cursor.exactUsageUncertain = true + line = nil + } + if errors.Is(readErr, bufio.ErrBufferFull) { + continue + } + if len(chunk) > 0 && chunk[len(chunk)-1] == '\n' { + cursor.offset = decoded + copy(cursor.compressedDigest[:], digest.Sum(nil)) + cursor.compressedTail = append(cursor.compressedTail[:0], tail...) + if !oversized { + baselineRolloutRecord(cursor, line) + } + line = line[:0] + oversized = false + } + if readErr != nil { + if !errors.Is(readErr, io.EOF) { + return nil, readErr + } + break + } + } + cursor.exactUsageUncertain = cursor.exactUsageUncertain && !cursor.exactUsage + if cursor.threadID == "" { + return nil, errors.New("compressed history omitted session metadata") + } + return cursor, nil +} + +func baselineRolloutRecord(c *rolloutCursor, line []byte) { + if metadata, ok := rolloutMetadataRecord(line); ok && c.threadID == "" { + c.threadID, c.parentThreadID, c.workingDirectory = metadata.ID, metadata.ParentThreadID, metadata.WorkingDirectory + c.startedAt, c.nonRoot, c.subagentHistoryStartOrdinal = metadata.StartedAt, metadata.NonRoot, metadata.SubagentHistoryStartOrdinal + } + if settings, ordinal, at, ok := rolloutSessionSettingsRecord(line, c.modelSettings); ok && tokenRecordIsOwned(ordinal, c.subagentHistoryStartOrdinal, at, c.nonRoot, c.startedAt) { + c.modelSettings = settings + } + if tier, ordinal, at, ok := rolloutTierRecord(line); ok && tokenRecordIsOwned(ordinal, c.subagentHistoryStartOrdinal, at, c.nonRoot, c.startedAt) { + c.nextServiceTier = tier + } + if model, ordinal, at, ok := rolloutModelRecord(line); ok && tokenRecordIsOwned(ordinal, c.subagentHistoryStartOrdinal, at, c.nonRoot, c.startedAt) { + c.currentModel, c.currentServiceTier = model, c.nextServiceTier + } + if turn, ordinal, at, ok := rolloutTurnIDRecord(line); ok && tokenRecordIsOwned(ordinal, c.subagentHistoryStartOrdinal, at, c.nonRoot, c.startedAt) { + c.currentTurnID = turn + } + if attention, ordinal, at, ok := sessionAttentionRecord(line); ok && tokenRecordIsOwned(ordinal, c.subagentHistoryStartOrdinal, at, c.nonRoot, c.startedAt) { + c.attention = attention + } + if preview, ok := rolloutContextRecord(line, c); ok { + c.preview = preview + } + if record, ok := tokenUsageRecord(line); ok { + c.totalTokens = record.total + } + if record, ok := exactUsageRecord(line); ok && c.ownsExactRecord(record) { + c.exactUsage = true + usage := BenchmarkUsage{} + if record.Payload.Usage != nil { + usage = record.Payload.Usage.benchmarkUsage() + } + if id := strings.TrimSpace(record.Payload.ResponseID); id != "" { + c.rememberExactResponse(id, usage) + } + } +} + +func (r *LiveUsageReader) restoreCompressedCursor(path string, info os.FileInfo, cursor *rolloutCursor) error { + file, err := os.Open(path) + if err != nil { + return err + } + tail := make([]byte, len(cursor.compressedTail)) + n, readErr := file.ReadAt(tail, cursor.offset-int64(len(tail))) + // The tail is a quick mismatch check; hash the entire bounded prefix before + // trusting its settings and offset. Reverts may change earlier context while + // retaining an identical tail and file length. + matches := false + if readErr == nil && n == len(tail) && bytes.Equal(tail, cursor.compressedTail) && info.Size() >= cursor.offset { + digest := sha256.New() + _, err = io.Copy(digest, io.NewSectionReader(file, 0, cursor.offset)) + if err != nil { + file.Close() + return err + } + matches = bytes.Equal(digest.Sum(nil), cursor.compressedDigest[:]) + } + file.Close() + if matches { + cursor.compressed = false + cursor.compressedInfo = nil + cursor.compressedTail = nil + cursor.lastModified = info.ModTime() + return nil + } + // A changed/truncated restoration cannot reuse an offset from different + // history. Existing-thread discovery rebases it rather than billing copies. + return r.addFile(path, info) +} diff --git a/internal/codex/live_usage_compressed_test.go b/internal/codex/live_usage_compressed_test.go new file mode 100644 index 0000000..80002a0 --- /dev/null +++ b/internal/codex/live_usage_compressed_test.go @@ -0,0 +1,253 @@ +package codex + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/klauspost/compress/zstd" +) + +func compressTestRollout(t *testing.T, path string, remove bool) []byte { + t.Helper() + plain, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + encoder, err := zstd.NewWriter(nil) + if err != nil { + t.Fatal(err) + } + data := encoder.EncodeAll(plain, nil) + encoder.Close() + if err := os.WriteFile(path+".zst", data, 0600); err != nil { + t.Fatal(err) + } + if remove { + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + } + return plain +} + +func TestCompressedHistoryRestorationCountsOnlyFreshResponses(t *testing.T) { + _, path, now := astraUsageReader(t, 100) + usage := BenchmarkUsage{InputTokens: 1000, OutputTokens: 100, TotalTokens: 1100} + old := exactLine(t, now, "astra-accounting", "turn", "historical", usage) + "\n" + appendRollout(t, path, old) + plain := compressTestRollout(t, path, true) + home := strings.Split(path, string(filepath.Separator)+"sessions"+string(filepath.Separator))[0] + r, err := NewLiveUsageReader(home) + if err != nil { + t.Fatal(err) + } + r.statusProvider = nil + baseline, err := r.FetchTokenUsage(context.Background()) + if err != nil || baseline.TotalTokens != 0 || len(r.files) != 1 || !r.files[path].compressed || !r.files[path].exactUsage { + t.Fatal("compressed history was lost or billed", baseline, err) + } + // Codex restores history and can append before our next discovery pass. + if err := os.WriteFile(path, plain, 0600); err != nil { + t.Fatal(err) + } + appendRollout(t, path, old+exactLine(t, now, "astra-accounting", "turn", "new", usage)+"\n") + fresh, err := r.FetchTokenUsageFresh(context.Background()) + if err != nil || fresh.TotalTokens != 1100 || fresh.APIEqPricedCalls != 1 || len(r.files) != 1 || r.files[path].compressed { + t.Fatal("restoration billed history or missed append", fresh, err) + } + next, err := r.FetchTokenUsageFresh(context.Background()) + if err != nil || next.TotalTokens != fresh.TotalTokens || next.APIEqUSD != fresh.APIEqUSD { + t.Fatal("plain/compressed siblings duplicated accounting", next, err) + } +} + +func TestCompressionWhileObservedPreservesSessionTotals(t *testing.T) { + r, path, now := astraUsageReader(t, 100) + r.statusProvider = nil + usage := BenchmarkUsage{InputTokens: 1000, OutputTokens: 100, TotalTokens: 1100} + appendRollout(t, path, exactLine(t, now, "astra-accounting", "turn", "first", usage)+"\n") + before, err := r.FetchTokenUsage(context.Background()) + if err != nil { + t.Fatal(err) + } + plain := compressTestRollout(t, path, true) + compressed, err := r.FetchTokenUsageFresh(context.Background()) + if err != nil || compressed.TotalTokens != before.TotalTokens || compressed.APIEqUSD != before.APIEqUSD || len(r.files) != 1 || !r.files[path].compressed { + t.Fatal("compression discarded or recounted observed history", compressed, err) + } + if err := os.WriteFile(path, plain, 0600); err != nil { + t.Fatal(err) + } + appendRollout(t, path, exactLine(t, now, "astra-accounting", "turn", "second", usage)+"\n") + restored, err := r.FetchTokenUsage(context.Background()) + if err != nil || restored.TotalTokens != 2200 || restored.APIEqPricedCalls != 2 || restored.Sessions[0].TotalTokens != 2200 { + t.Fatal("restoration lost existing totals", restored, err) + } +} + +func TestChangedCompressedRestorationRebasesCopiedHistory(t *testing.T) { + _, path, now := astraUsageReader(t, 100) + usage := BenchmarkUsage{InputTokens: 1000, OutputTokens: 100, TotalTokens: 1100} + appendRollout(t, path, exactLine(t, now, "astra-accounting", "turn", "old", usage)+"\n") + plain := compressTestRollout(t, path, true) + home := strings.Split(path, string(filepath.Separator)+"sessions"+string(filepath.Separator))[0] + r, err := NewLiveUsageReader(home) + if err != nil { + t.Fatal(err) + } + r.statusProvider = nil + if _, err := r.FetchTokenUsage(context.Background()); err != nil { + t.Fatal(err) + } + // Revert/rewrite changes the baseline prefix; none of this is fresh spend. + if err := os.WriteFile(path, []byte(strings.ReplaceAll(string(plain), "old", "rewritten")), 0600); err != nil { + t.Fatal(err) + } + rebased, err := r.FetchTokenUsageFresh(context.Background()) + if err != nil || rebased.TotalTokens != 0 || rebased.APIEqPricedCalls != 0 || rebased.APIEqAccountingAdjustments != 1 { + t.Fatal("changed restoration billed copied history", rebased, err) + } +} + +func TestCorruptCompressedHistoryIsRejected(t *testing.T) { + _, path, _ := astraUsageReader(t, 100) + if err := os.Remove(path); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path+".zst", []byte("invalid zstd"), 0600); err != nil { + t.Fatal(err) + } + home := strings.Split(path, string(filepath.Separator)+"sessions"+string(filepath.Separator))[0] + r, err := NewLiveUsageReader(home) + if err != nil { + t.Fatal(err) + } + r.statusProvider = nil + if _, err := r.FetchTokenUsage(context.Background()); err == nil { + t.Fatal("corrupt compressed history accepted") + } + if len(r.files) != 0 { + t.Fatal("partial compressed baseline retained") + } +} + +func TestCompressedRestorationChecksContextBeforeUnchangedTail(t *testing.T) { + _, path, now := astraUsageReader(t, 100) + // Put changed model context outside the suffix check, retaining byte length. + appendRollout(t, path, strings.Repeat(`{"type":"response_item","payload":"padding"}`+"\n", 200)) + usage := BenchmarkUsage{InputTokens: 1000, OutputTokens: 100, TotalTokens: 1100} + appendRollout(t, path, exactLine(t, now, "astra-accounting", "turn", "old", usage)+"\n") + plain := compressTestRollout(t, path, true) + home := strings.Split(path, string(filepath.Separator)+"sessions"+string(filepath.Separator))[0] + r, err := NewLiveUsageReader(home) + if err != nil { + t.Fatal(err) + } + r.statusProvider = nil + if _, err := r.FetchTokenUsage(context.Background()); err != nil { + t.Fatal(err) + } + changed := strings.ReplaceAll(string(plain), "gpt-6-astra", "gpt-6.1-sol") + if len(changed) != len(plain) || changed == string(plain) { + t.Fatal("test did not preserve history size") + } + if err := os.WriteFile(path, []byte(changed), 0600); err != nil { + t.Fatal(err) + } + got, err := r.FetchTokenUsageFresh(context.Background()) + if err != nil || got.APIEqAccountingAdjustments != 1 || got.APIEqPricedCalls != 0 || r.files[path].currentModel != "gpt-6.1-sol" { + t.Fatal("changed context trusted because suffix matched", got, err) + } +} + +func TestCompressedDiscoveryWarningSurvivesEarlierPlainReadFailure(t *testing.T) { + r, _, _ := astraUsageReader(t, 100) + r.statusProvider = nil + // WalkDir visits the unreadable plain rollout before the corrupt compressed + // one. A third, healthy cursor keeps discovery usable rather than fatal. + denied := filepath.Join(r.SessionsRoot, "rollout-a.jsonl") + if err := os.WriteFile(denied, []byte("unreadable"), 0600); err != nil { + t.Fatal(err) + } + if err := os.Chmod(denied, 0); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = os.Chmod(denied, 0600) }) + if file, err := os.Open(denied); err == nil { + file.Close() + t.Skip("filesystem permits reading files with mode 000") + } + corrupt := filepath.Join(r.SessionsRoot, "rollout-z.jsonl.zst") + if err := os.WriteFile(corrupt, []byte("corrupt zstd"), 0600); err != nil { + t.Fatal(err) + } + got, err := r.FetchTokenUsageFresh(context.Background()) + if err != nil || !strings.Contains(got.APIEqCoverageIssue, "compressed rollout history") || len(r.files) != 1 { + t.Fatal("earlier read failure suppressed compressed coverage warning", got.APIEqCoverageIssue, err) + } +} + +func TestRewrittenCompressedRestorePreservesObservedActivity(t *testing.T) { + r, path, now := astraUsageReader(t, 100) + r.statusProvider = nil + usage := BenchmarkUsage{InputTokens: 1000, OutputTokens: 100, TotalTokens: 1100} + appendRollout(t, path, exactLine(t, now, "astra-accounting", "turn", "first", usage)+"\n"+turnTimingLine(now, 42)+"\n") + before, err := r.FetchTokenUsage(context.Background()) + if err != nil || len(before.Sessions) != 1 || len(before.Sessions[0].ModelCalls) != 1 || len(before.Sessions[0].TurnTimings) != 1 { + t.Fatal("observed activity fixture missing", before, err) + } + observed := before.Sessions[0] + plain := compressTestRollout(t, path, true) + if _, err := r.FetchTokenUsageFresh(context.Background()); err != nil { + t.Fatal(err) + } + rewritten := strings.ReplaceAll(string(plain), "first", "rewritten") + if err := os.WriteFile(path, []byte(rewritten), 0600); err != nil { + t.Fatal(err) + } + rebased, err := r.FetchTokenUsageFresh(context.Background()) + if err != nil || rebased.APIEqAccountingAdjustments != 1 || rebased.TotalTokens != before.TotalTokens || rebased.APIEqUSD != before.APIEqUSD || len(rebased.Sessions) != 1 { + t.Fatal("rewrite changed reader-wide accounting", rebased, err) + } + session := rebased.Sessions[0] + if session.TotalTokens != observed.TotalTokens || len(session.ModelCalls) != 1 || session.ModelCalls[0] != observed.ModelCalls[0] || len(session.TurnTimings) != 1 || session.TurnTimings[0] != observed.TurnTimings[0] { + t.Fatal("rewrite discarded observed session history", session) + } + appendRollout(t, path, exactLine(t, now, "astra-accounting", "turn", "fresh", usage)+"\n") + fresh, err := r.FetchTokenUsage(context.Background()) + if err != nil || fresh.TotalTokens != 2200 || fresh.APIEqPricedCalls != 2 || fresh.Sessions[0].TotalTokens != 2200 || len(fresh.Sessions[0].ModelCalls) != 2 || len(fresh.Sessions[0].TurnTimings) != 1 { + t.Fatal("fresh response did not extend preserved history", fresh, err) + } +} + +func TestReplacingRolloutWithDifferentThreadDoesNotInheritActivity(t *testing.T) { + r, path, now := astraUsageReader(t, 100) + r.statusProvider = nil + usage := BenchmarkUsage{InputTokens: 1000, OutputTokens: 100, TotalTokens: 1100} + appendRollout(t, path, exactLine(t, now, "astra-accounting", "turn", "first", usage)+"\n"+turnTimingLine(now, 42)+"\n") + if _, err := r.FetchTokenUsage(context.Background()); err != nil { + t.Fatal(err) + } + original, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + changed := strings.ReplaceAll(string(original), "astra-accounting", "different-thread") + if err := os.WriteFile(path, []byte(changed), 0600); err != nil { + t.Fatal(err) + } + info, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + if err := r.addFile(path, info); err != nil { + t.Fatal(err) + } + cursor := r.files[path] + if cursor.threadID != "different-thread" || cursor.observedTokens != 0 || len(cursor.modelCalls) != 0 || len(cursor.turnTimings) != 0 { + t.Fatal("observed activity crossed thread identities", cursor) + } +} diff --git a/internal/codex/live_usage_exact_test.go b/internal/codex/live_usage_exact_test.go index eac8339..696348c 100644 --- a/internal/codex/live_usage_exact_test.go +++ b/internal/codex/live_usage_exact_test.go @@ -278,3 +278,43 @@ func TestExactUsageOlderThanStartupTailCannotEnableLegacyBilling(t *testing.T) { } } } + +// Codex 0.163+ persists spending from incomplete Responses API requests even +// when the turn fails. These records must not depend on a successful turn or +// a matching (potentially recomputed) context counter. +func TestExactIncompleteResponseSpendingSurvivesFailedTurn(t *testing.T) { + r, path, now := astraUsageReader(t, 100) + r.statusProvider = nil + usage := BenchmarkUsage{InputTokens: 1000, CachedInputTokens: 200, OutputTokens: 100, ReasoningOutputTokens: 80, TotalTokens: 1100} + response := exactLine(t, now, "astra-accounting", "failed-turn", "incomplete-response", usage) + "\n" + appendRollout(t, path, response+`{"type":"event_msg","payload":{"type":"error","message":"Incomplete response returned, reason: max_output_tokens"}}`+"\n"+richTokenCountLine(now, 123456, 1000, 200, 0, 100)+"\n") + failed, err := r.FetchTokenUsage(context.Background()) + want, _, _ := EstimateStandardAPIEqCost("gpt-6-astra", usage) + if err != nil || failed.APIEqPricedCalls != 1 || failed.TotalTokens != 1100 || math.Abs(failed.APIEqUSD-want) > 1e-12 || failed.APIEqInconsistentUsageCalls != 0 { + t.Fatal("failed response spending was dropped or tied to context", failed, err) + } + // A reconnect replay and a retained-reasoning context correction are not + // additional spending, even though the context total moves backwards. + appendRollout(t, path, response+richTokenCountLine(now, 120000, 1000, 200, 0, 100)+"\n") + replayed, err := r.FetchTokenUsage(context.Background()) + if err != nil || replayed.TotalTokens != failed.TotalTokens || replayed.APIEqUSD != failed.APIEqUSD || replayed.APIEqPricedCalls != 1 || replayed.APIEqAccountingAdjustments != 0 { + t.Fatal("failed response replay or context correction was billed twice", replayed, err) + } +} + +func TestExactIncompleteResponseWithoutUsageRemainsMissing(t *testing.T) { + r, path, now := astraUsageReader(t, 100) + r.statusProvider = nil + line := exactLine(t, now, "astra-accounting", "failed-turn", "incomplete-response", BenchmarkUsage{}) + var record map[string]any + if err := json.Unmarshal([]byte(line), &record); err != nil { + t.Fatal(err) + } + record["payload"].(map[string]any)["usage"] = nil + raw, _ := json.Marshal(record) + appendRollout(t, path, string(raw)+"\n"+richTokenCountLine(now, 1200, 1000, 0, 0, 100)+"\n") + got, err := r.FetchTokenUsage(context.Background()) + if err != nil || got.APIEqMissingUsageCalls != 1 || got.APIEqPricedCalls != 0 || got.APIEqUnpricedCalls != 0 || got.TotalTokens != 0 || len(got.APIEqDiagnostics) != 1 || got.APIEqDiagnostics[0].ResponseID != "incomplete-response" { + t.Fatal("missing incomplete-response usage was guessed from context", got, err) + } +} diff --git a/internal/codex/session_commands_unix.go b/internal/codex/session_commands_unix.go index 9eb4450..b6e15fb 100644 --- a/internal/codex/session_commands_unix.go +++ b/internal/codex/session_commands_unix.go @@ -478,16 +478,26 @@ func (p *daemonStatusProvider) ExecuteSessionCommand(ctx context.Context, id, pa } } } - if m.Picker { + if permission, ok := o.params["permissions"].(string); ok { + return p.writeVerifiedPermissions(ctx, conn, id, permission, o.params) + } + _, changingModel := o.params["model"] + if m.Picker || changingModel { current, err := p.readQuotaSession(ctx, id) if err != nil { return err } + if model, ok := o.params["model"].(string); ok { + current.Model = model + } + if effort, ok := o.params["effort"].(string); ok { + current.Effort = effort + } current.Tier = nil if tier, ok := o.params["serviceTier"].(string); ok { current.Tier = &tier } - return p.writeQuotaSettings(ctx, current, o.params) + return p.writeQuotaSettingsOn(ctx, conn, current, o.params) } return p.requestOn(ctx, conn, o.method, o.params, nil) } @@ -516,7 +526,7 @@ func (p *daemonStatusProvider) commandCatalogue(ctx context.Context, t commandTh switch path { case "skills", "hooks": params["cwds"] = []string{t.Cwd} - case "apps", "mcp": + case "apps", "mcp", "experimental": params["threadId"] = t.ID } rows, err := p.commandRows(ctx, method, params) diff --git a/internal/codex/session_compatibility_unix_test.go b/internal/codex/session_compatibility_unix_test.go new file mode 100644 index 0000000..4c45b8f --- /dev/null +++ b/internal/codex/session_compatibility_unix_test.go @@ -0,0 +1,138 @@ +//go:build unix + +package codex + +import ( + "context" + "encoding/json" + "errors" + "strings" + "testing" + "time" +) + +func TestSettingsCompatibilityAcrossClientsAndReconnect(t *testing.T) { + for _, compressed := range []bool{false, true} { + t.Run(map[bool]string{false: "plain", true: "zstd"}[compressed], func(t *testing.T) { + p, f := newQuotaDaemonTransport(t, compressed) + f.catalogues = map[string]map[string]any{ + "permissionProfile/list": {"data": []any{map[string]any{"id": "restricted", "description": "Workspace access only", "allowed": true}}}, + "experimentalFeature/list": {"data": []any{}}, + } + ctx := context.Background() + // A second attached client reads the same persisted runtime selections. + second := &daemonStatusProvider{socketPath: p.socketPath} + t.Cleanup(func() { second.disconnect(nil) }) + model, err := p.SessionCommands(ctx, "one", "model/small") + if err != nil { + t.Fatal(err) + } + if err = p.ExecuteSessionCommand(ctx, "one", model.Path, model.Revision, model.Choices[0].ID); err != nil { + t.Fatal(err) + } + permissions, err := p.SessionCommands(ctx, "one", "permissions") + if err != nil || len(permissions.Choices) != 1 { + t.Fatal(permissions, err) + } + if err = p.ExecuteSessionCommand(ctx, "one", permissions.Path, permissions.Revision, permissions.Choices[0].ID); err != nil { + t.Fatal(err) + } + if err = second.ensureConnected(ctx); err != nil { + t.Fatal(err) + } + current, err := second.readQuotaSession(ctx, "one") + if err != nil || current.Model != "small" || current.Effort != "medium" || current.Tier != nil { + t.Fatal("second client lost applied selections", current, err) + } + p.disconnect(nil) + if err = p.ensureConnected(ctx); err != nil { + t.Fatal(err) + } + resumed, err := p.readQuotaSession(ctx, "one") + if err != nil || !sameQuotaSettings(current, resumed) { + t.Fatal("reconnect replaced saved settings", resumed, err) + } + f.mu.Lock() + defer f.mu.Unlock() + if f.permissions["one"] != "restricted" || f.sessions["two"].Model != "large" || len(f.writes) != 2 { + t.Fatal("settings escaped selected thread", f.writes) + } + for _, read := range f.resumeReads { + if len(read) != 2 || read["threadId"] == nil || read["excludeTurns"] != true { + t.Fatal("read-only resume supplied overrides", read) + } + } + }) + } +} + +func TestQueuedModelAndPermissionAcknowledgementsAreNotApplied(t *testing.T) { + for _, path := range []string{"model/small", "permissions"} { + t.Run(path, func(t *testing.T) { + p, f := newQuotaDaemonTransport(t, true) + f.queued = true + f.catalogues = map[string]map[string]any{"permissionProfile/list": {"data": []any{map[string]any{"id": "restricted", "description": "Workspace access only", "allowed": true}}}} + menu, err := p.SessionCommands(context.Background(), "one", path) + if err != nil || len(menu.Choices) != 1 { + t.Fatal(menu, err) + } + ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond) + defer cancel() + done := make(chan error, 1) + go func() { done <- p.ExecuteSessionCommand(ctx, "one", menu.Path, menu.Revision, menu.Choices[0].ID) }() + // State updates for another attached session and approval state remain + // responsive while the selected thread's queued write is being verified. + p.handleNotification("thread/settings/updated", json.RawMessage(`{"threadId":"two","threadSettings":{"model":"other","effort":"high","serviceTier":"priority"}}`)) + responsive := make(chan struct{}) + go func() { p.SessionPrompt("two"); close(responsive) }() + select { + case <-responsive: + case <-time.After(time.Second): + t.Fatal("settings verification froze event state") + } + err = <-done + if err == nil { + t.Fatal("queued acknowledgement reported as applied") + } + if path == "model/small" && !errors.Is(err, ErrQuotaProfileUnverified) { + t.Fatal("model failure lost unverified result", err) + } + if path == "permissions" && !strings.Contains(err.Error(), "unconfirmed") { + t.Fatal("permission failure lost unconfirmed result", err) + } + f.mu.Lock() + writes := len(f.writes) + f.mu.Unlock() + if writes != 1 { + t.Fatal("queued mutation was retried", writes) + } + }) + } +} + +func TestAccountChangeInvalidatesCatalogueWithoutDiscardingConversation(t *testing.T) { + p, f := newQuotaDaemon(t) + f.catalogues = map[string]map[string]any{"experimentalFeature/list": {"data": []any{}}} + if _, ok := p.Fetch(context.Background(), []string{"one"}); !ok { + t.Fatal("daemon unavailable") + } + c := Client{LiveUsage: &LiveUsageReader{statusProvider: p}} + if _, err := c.SessionCommands(context.Background(), "one", "model/small"); err != nil { + t.Fatal(err) + } + p.mu.Lock() + if p.contexts == nil { + p.contexts = make(map[string]*daemonContextState) + } + p.contexts["one"] = &daemonContextState{latest: SessionContext{Text: "retained reply"}} + p.mu.Unlock() + p.handleNotification("account/updated", json.RawMessage(`{"authMode":"chatgpt","planType":"pro"}`)) + if _, ok := c.CachedSessionCommands("one", "model/small"); ok { + t.Fatal("account change retained model catalogue") + } + p.mu.Lock() + defer p.mu.Unlock() + if p.contexts["one"].latest.Text != "retained reply" || !p.lastStatusAt.IsZero() { + t.Fatal("account change discarded conversation or skipped refresh") + } +} diff --git a/internal/codex/session_permissions_unix.go b/internal/codex/session_permissions_unix.go new file mode 100644 index 0000000..42bbbb7 --- /dev/null +++ b/internal/codex/session_permissions_unix.go @@ -0,0 +1,49 @@ +//go:build unix + +package codex + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/gorilla/websocket" +) + +// A settings acknowledgement means queued. Read the loaded thread without +// overrides until native permission provenance confirms the chosen profile. +// This neither waits under the event-state lock nor retries the mutation. +func (p *daemonStatusProvider) writeVerifiedPermissions(ctx context.Context, conn *websocket.Conn, id, permission string, params map[string]any) error { + if err := p.requestOn(ctx, conn, "thread/settings/update", params, nil); err != nil { + return err + } + verify, cancel := context.WithTimeout(ctx, 3*time.Second) + defer cancel() + ticker := time.NewTicker(50 * time.Millisecond) + defer ticker.Stop() + for { + loaded, err := p.loadedThreads(verify) + if err != nil { + return fmt.Errorf("permission update unconfirmed: %w", err) + } + if _, ok := loaded[id]; !ok { + return errors.New("permission update unconfirmed: session unloaded") + } + var result struct { + Thread struct{ ID string } + ActivePermissionProfile *struct{ ID string } + } + if err := p.requestOn(verify, conn, "thread/resume", map[string]any{"threadId": id, "excludeTurns": true}, &result); err != nil { + return fmt.Errorf("permission update unconfirmed: %w", err) + } + if result.Thread.ID == id && result.ActivePermissionProfile != nil && result.ActivePermissionProfile.ID == permission { + return nil + } + select { + case <-verify.Done(): + return fmt.Errorf("permission update queued but application unconfirmed: %w", verify.Err()) + case <-ticker.C: + } + } +} diff --git a/internal/codex/session_settings_unix.go b/internal/codex/session_settings_unix.go index 0091bda..26272c3 100644 --- a/internal/codex/session_settings_unix.go +++ b/internal/codex/session_settings_unix.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/gorilla/websocket" "sort" "strings" "time" @@ -145,12 +146,19 @@ func (p *daemonStatusProvider) validateQuotaStep(ctx context.Context, step Quota // The RPC acknowledgement means queued, not applied. Observe the actual // configured settings before calling an update successful. -func (p *daemonStatusProvider) writeQuotaSettings(ctx context.Context, expected QuotaSession, params map[string]any) (err error) { +func (p *daemonStatusProvider) writeQuotaSettings(ctx context.Context, expected QuotaSession, params map[string]any) error { + p.mu.Lock() + conn := p.connection + p.mu.Unlock() + return p.writeQuotaSettingsOn(ctx, conn, expected, params) +} + +func (p *daemonStatusProvider) writeQuotaSettingsOn(ctx context.Context, conn *websocket.Conn, expected QuotaSession, params map[string]any) (err error) { if err := ctx.Err(); err != nil { return err } _, _, before, _ := p.observedQuotaSettings(expected.ID) - if err := p.request(ctx, "thread/settings/update", params, nil); err != nil { + if err := p.requestOn(ctx, conn, "thread/settings/update", params, nil); err != nil { var rejection *daemonResponseError if !errors.As(err, &rejection) { return errors.Join(ErrQuotaProfileUncertain, err) @@ -165,6 +173,12 @@ func (p *daemonStatusProvider) writeQuotaSettings(ctx context.Context, expected deadline := time.NewTimer(3 * time.Second) defer deadline.Stop() for { + p.mu.Lock() + sameConnection := p.connection == conn + p.mu.Unlock() + if !sameConnection { + return errors.New("connection changed before settings were verified") + } observed, changed, version, ok := p.observedQuotaSettings(expected.ID) if ok && version > before && sameQuotaSettings(observed, expected) { return nil diff --git a/internal/codex/session_settings_unix_test.go b/internal/codex/session_settings_unix_test.go index 88539ff..e8fbfc6 100644 --- a/internal/codex/session_settings_unix_test.go +++ b/internal/codex/session_settings_unix_test.go @@ -33,6 +33,8 @@ type quotaDaemonFixture struct { cwds map[string]string ignoreCwd bool environments []any + permissions map[string]string + resumeReads []map[string]any } func TestQuotaEffortDefaultFallback(t *testing.T) { @@ -58,6 +60,10 @@ func TestQuotaEffortDefaultFallback(t *testing.T) { } func newQuotaDaemon(t *testing.T) (*daemonStatusProvider, *quotaDaemonFixture) { + return newQuotaDaemonTransport(t, false) +} + +func newQuotaDaemonTransport(t *testing.T, compressed bool) (*daemonStatusProvider, *quotaDaemonFixture) { t.Helper() fixture := "aDaemonFixture{sessions: map[string]QuotaSession{"one": {ID: "one", Model: "large", Effort: "high"}, "two": {ID: "two", Model: "large", Effort: "high"}}} dir, err := os.MkdirTemp("", "cxq-") @@ -75,18 +81,27 @@ func newQuotaDaemon(t *testing.T) (*daemonStatusProvider, *quotaDaemonFixture) { } upgrader := websocket.Upgrader{} server := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) + headers := http.Header{} + if compressed { + headers.Set(daemonCompressionHeader, daemonCompressionProtocol) + } + conn, err := upgrader.Upgrade(w, r, headers) if err != nil { return } - defer conn.Close() + wire, err := newDaemonWire(conn, headers) + if err != nil { + conn.Close() + return + } + defer wire.close() for { var req struct { ID *int64 Method string Params map[string]any } - if conn.ReadJSON(&req) != nil { + if wire.readJSON(&req) != nil { return } if req.ID == nil { @@ -109,13 +124,14 @@ func newQuotaDaemon(t *testing.T) (*daemonStatusProvider, *quotaDaemonFixture) { } result["data"] = ids case "thread/resume": + fixture.resumeReads = append(fixture.resumeReads, req.Params) s, ok := fixture.sessions[id] failure = !ok || fixture.fail == id cwd := fixture.cwds[id] if cwd == "" { cwd = "/work" } - result = map[string]any{"cwd": cwd, "model": s.Model, "reasoningEffort": s.Effort, "serviceTier": s.Tier, "thread": map[string]any{"id": id}} + result = map[string]any{"cwd": cwd, "model": s.Model, "reasoningEffort": s.Effort, "serviceTier": s.Tier, "thread": map[string]any{"id": id}, "activePermissionProfile": map[string]any{"id": fixture.permissions[id]}} case "thread/read": status := fixture.commandStatus if status == "" { @@ -159,6 +175,12 @@ func newQuotaDaemon(t *testing.T) (*daemonStatusProvider, *quotaDaemonFixture) { } } if !fixture.queued { + if permission, ok := req.Params["permissions"].(string); ok { + if fixture.permissions == nil { + fixture.permissions = make(map[string]string) + } + fixture.permissions[id] = permission + } fixture.sessions[id] = desired } if fixture.notify { @@ -193,10 +215,10 @@ func newQuotaDaemon(t *testing.T) (*daemonStatusProvider, *quotaDaemonFixture) { delete(response, "result") response["error"] = map[string]any{"code": -1, "message": "test failure"} } - if conn.WriteJSON(response) != nil { + if wire.writeJSON(response) != nil { return } - if settingsNotification != nil && conn.WriteJSON(settingsNotification) != nil { + if settingsNotification != nil && wire.writeJSON(settingsNotification) != nil { return } } diff --git a/internal/codex/session_stream_test.go b/internal/codex/session_stream_test.go index a06d391..6010f56 100644 --- a/internal/codex/session_stream_test.go +++ b/internal/codex/session_stream_test.go @@ -107,3 +107,26 @@ func TestStreamingCommentaryKeepsCommandAndApprovalPriority(t *testing.T) { t.Fatal("stream revived after interruption") } } + +func TestSettingsNotificationsDoNotResolveOtherClientApprovals(t *testing.T) { + states := map[string]*daemonContextState{} + now := time.Now() + for _, thread := range []string{"one", "two"} { + raw := `{"threadId":"` + thread + `","turnId":"turn","itemId":"item","command":"go test","cwd":"/work"}` + daemonContextEvent(states, "item/commandExecution/requestApproval", json.RawMessage(`"same-id"`), json.RawMessage(raw), now) + } + daemonContextEvent(states, "thread/settings/updated", nil, json.RawMessage(`{"threadId":"one","threadSettings":{"model":"changed","effort":"high"}}`), now) + if len(states["one"].requests) != 1 || len(states["two"].requests) != 1 { + t.Fatal("settings discarded pending approvals") + } + daemonContextEvent(states, "serverRequest/resolved", nil, json.RawMessage(`{"threadId":"one","requestId":"same-id"}`), now) + if len(states["one"].requests) != 0 || len(states["two"].requests) != 1 { + t.Fatal("resolution crossed thread/client boundaries") + } + daemonContextEvent(states, "turn/started", nil, json.RawMessage(`{"threadId":"one","turn":{"id":"next"}}`), now) + daemonContextEvent(states, "item/started", nil, json.RawMessage(`{"threadId":"one","turnId":"next","item":{"type":"agentMessage","id":"reply","phase":"final_answer"}}`), now) + daemonContextEvent(states, "item/agentMessage/delta", nil, json.RawMessage(`{"threadId":"one","turnId":"next","itemId":"reply","delta":"Still updating"}`), now) + if states["one"].latest.Text != "Still updating" || len(states["two"].requests) != 1 { + t.Fatal("stream stalled after settings/approval resolution") + } +} diff --git a/internal/codex/testdata/zstd-stream-v1.json b/internal/codex/testdata/zstd-stream-v1.json new file mode 100644 index 0000000..1dd9935 --- /dev/null +++ b/internal/codex/testdata/zstd-stream-v1.json @@ -0,0 +1,14 @@ +[ + { + "json": "{\"id\":0,\"result\":{\"text\":\"Native Codex-compatible stream history abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef\"}}", + "hex": "28b52ffd00482c0300c4057b226964223a302c22726573756c74223a7b2274657874223a224e617469766520436f6465782d636f6d70617469626c652073747265616d20686973746f727920616263646566616263646566616263646566616263646566227d7d0100598fad6614" + }, + { + "json": "{\"id\":1,\"result\":{\"text\":\"Native Codex-compatible stream history abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef\"}}", + "hex": "7c0000387b226964223a310100923e55c208" + }, + { + "json": "{\"id\":2,\"result\":{\"text\":\"Native Codex-compatible stream history abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef\"}}", + "hex": "740000387b226964223a320100922a6004" + } +] diff --git a/internal/codex/usage_coverage.go b/internal/codex/usage_coverage.go new file mode 100644 index 0000000..b32604b --- /dev/null +++ b/internal/codex/usage_coverage.go @@ -0,0 +1,162 @@ +package codex + +import ( + "context" + "errors" + "io" + "os" + "path/filepath" + "sort" + "time" + + "github.com/pelletier/go-toml/v2" +) + +const asyncCompactionCoverage = "background compaction is enabled; Codex does not publish all background response usage" + +type localFeatureCheck struct { + info os.FileInfo + enabled bool + issue string +} + +// A positive local flag is enough to qualify file-only accounting. A negative +// file read cannot establish hidden per-process CLI overrides; authoritative +// loaded-thread feature state is preferred whenever the daemon supplies it. +func (r *LiveUsageReader) usageCoverage(ctx context.Context, sessions []LiveUsageSession, daemon map[string]string) string { + if r.knownUsageCoverage == nil { + r.knownUsageCoverage = make(map[string]string) + } + for id, issue := range daemon { + r.knownUsageCoverage[id] = issue + } + issues := []string{} + // Children are folded into their root's display row. Preserve independent + // feature coverage for loaded/recent children before that grouping hides IDs. + candidates := append([]LiveUsageSession(nil), sessions...) + seen := make(map[string]bool) + for _, session := range sessions { + seen[session.ID] = true + } + for _, cursor := range r.files { + recent := time.Since(cursor.lastModified) <= activeRolloutHorizon + if !seen[cursor.threadID] && cursor.threadID != "" && (recent || r.knownUsageCoverage[cursor.threadID] != "") { + candidates = append(candidates, LiveUsageSession{ID: cursor.threadID, WorkingDirectory: cursor.workingDirectory, Active: recent}) + seen[cursor.threadID] = true + } + } + for id := range r.knownUsageCoverage { + if !seen[id] { + delete(r.knownUsageCoverage, id) + } + } + for _, session := range candidates { + issue, verified := daemon[session.ID] + if !verified { + issue = r.knownUsageCoverage[session.ID] + } + // Missing writer/status signals after disconnect do not prove recovery. + // Keep known risks even when their retained session row becomes inactive. + if !session.Active && issue == "" { + continue + } + if issue == "" && !verified { + issue = r.localUsageCoverage(ctx, session.WorkingDirectory) + } + if issue != "" { + id := []rune(SanitizeSessionContext(session.ID)) + issues = append(issues, "SESSION "+string(id[:min(12, len(id))])+": "+issue) + } + } + if r.discoveryCoverageIssue != "" { + issues = append(issues, r.discoveryCoverageIssue) + } + if len(issues) == 0 { + return "" + } + sort.Strings(issues) + return issues[0] +} + +func (r *LiveUsageReader) localUsageCoverage(ctx context.Context, cwd string) string { + paths := []string{filepath.Join(filepath.Dir(r.SessionsRoot), "config.toml")} + if filepath.IsAbs(cwd) { + for dir := filepath.Clean(cwd); ; dir = filepath.Dir(dir) { + paths = append(paths, filepath.Join(dir, ".codex", "config.toml")) + if filepath.Dir(dir) == dir { + break + } + } + } + for _, path := range paths { + if err := ctx.Err(); err != nil { + return "background usage configuration check interrupted" + } + enabled, issue := r.localAsyncCompaction(path) + if issue != "" { + return issue + } + if enabled { + return "local configuration can enable background compaction; effective usage coverage is unverified" + } + } + return "" +} + +func (r *LiveUsageReader) localAsyncCompaction(path string) (bool, string) { + info, err := os.Stat(path) + if errors.Is(err, os.ErrNotExist) { + return false, "" + } + if err != nil { + return false, "background usage configuration could not be read" + } + if r.localFeatureChecks == nil { + r.localFeatureChecks = make(map[string]localFeatureCheck) + } + if previous, ok := r.localFeatureChecks[path]; ok && os.SameFile(previous.info, info) && previous.info.ModTime() == info.ModTime() && previous.info.Size() == info.Size() { + return previous.enabled, previous.issue + } + file, err := os.Open(path) + if err != nil { + return false, "background usage configuration could not be read" + } + data, err := io.ReadAll(io.LimitReader(file, 1<<20+1)) + file.Close() + check := localFeatureCheck{info: info} + var config map[string]any + if err != nil || len(data) > 1<<20 || toml.Unmarshal(data, &config) != nil { + check.issue = "background usage configuration could not be verified" + } else { + check.enabled = hasAsyncCompaction(config) + } + // Only active-session configuration is relevant; retain a bounded cache. + if len(r.localFeatureChecks) >= 256 { + clear(r.localFeatureChecks) + } + r.localFeatureChecks[path] = check + return check.enabled, check.issue +} + +func hasAsyncCompaction(config map[string]any) bool { + if features, ok := config["features"].(map[string]any); ok { + switch value := features["async_compaction"].(type) { + case bool: + if value { + return true + } + case map[string]any: + if enabled, ok := value["enabled"].(bool); ok && enabled { + return true + } + } + } + if profiles, ok := config["profiles"].(map[string]any); ok { + for _, profile := range profiles { + if values, ok := profile.(map[string]any); ok && hasAsyncCompaction(values) { + return true + } + } + } + return false +} diff --git a/internal/codex/usage_coverage_test.go b/internal/codex/usage_coverage_test.go new file mode 100644 index 0000000..7a29242 --- /dev/null +++ b/internal/codex/usage_coverage_test.go @@ -0,0 +1,104 @@ +package codex + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" + "time" +) + +func TestUsageCoverageDetectsFileOnlyConfigurationAndPrefersEffectiveState(t *testing.T) { + home := t.TempDir() + project := filepath.Join(home, "project") + if err := os.MkdirAll(filepath.Join(project, ".codex"), 0700); err != nil { + t.Fatal(err) + } + path := filepath.Join(project, ".codex", "config.toml") + r := &LiveUsageReader{SessionsRoot: filepath.Join(home, "sessions")} + sessions := []LiveUsageSession{{ID: "thread", WorkingDirectory: project, Active: true}} + for _, config := range []string{"[features]\nasync_compaction = true\n", "[features.async_compaction]\nenabled = true\n", "[profiles.experimental.features]\nasync_compaction = true\n"} { + if err := os.WriteFile(path, []byte(config), 0600); err != nil { + t.Fatal(err) + } + r.localFeatureChecks = nil + if got := r.usageCoverage(context.Background(), sessions, nil); !strings.Contains(got, "background compaction") { + t.Fatal("file-only risk ignored", got) + } + if got := r.usageCoverage(context.Background(), sessions, map[string]string{"thread": ""}); got != "" { + t.Fatal("local setting overrode effective server state", got) + } + } + if err := os.WriteFile(path, []byte("[features]\nasync_compaction = false\n"), 0600); err != nil { + t.Fatal(err) + } + r.localFeatureChecks = nil + if got := r.usageCoverage(context.Background(), sessions, nil); got != "" { + t.Fatal("disabled flag blocked accounting", got) + } +} + +func TestUsageCoverageKeepsKnownRiskAcrossDisconnectAndIncludesChildren(t *testing.T) { + home := t.TempDir() + r := &LiveUsageReader{SessionsRoot: filepath.Join(home, "sessions"), files: map[string]*rolloutCursor{"child": {threadID: "child", workingDirectory: home, lastModified: time.Now()}}} + sessions := []LiveUsageSession{{ID: "root", WorkingDirectory: home, Active: true}} + for _, state := range []map[string]string{{"child": asyncCompactionCoverage}, nil} { + if got := r.usageCoverage(context.Background(), sessions, state); !strings.Contains(got, "child") || !strings.Contains(got, "background compaction") { + t.Fatal("child coverage vanished", got) + } + } + if got := r.usageCoverage(context.Background(), sessions, map[string]string{"child": ""}); got != "" { + t.Fatal("verified recovery retained stale risk", got) + } +} + +func TestUsageCoverageRejectsUnreadableConfigurationAndPartialDiscovery(t *testing.T) { + home := t.TempDir() + path := filepath.Join(home, "config.toml") + r := &LiveUsageReader{SessionsRoot: filepath.Join(home, "sessions")} + if err := os.WriteFile(path, []byte("malformed = ["), 0600); err != nil { + t.Fatal(err) + } + sessions := []LiveUsageSession{{ID: "thread", Active: true}} + if got := r.usageCoverage(context.Background(), sessions, nil); !strings.Contains(got, "could not be verified") { + t.Fatal("invalid configuration silently accepted", got) + } + r.discoveryCoverageIssue = "compressed rollout history could not be verified" + if got := r.usageCoverage(context.Background(), nil, nil); got != r.discoveryCoverageIssue { + t.Fatal("partial discovery hidden", got) + } +} + +func TestUsageCoverageRetainsInactiveSessionRiskUntilVerifiedRecovery(t *testing.T) { + for _, child := range []bool{false, true} { + t.Run(map[bool]string{false: "root", true: "old folded child"}[child], func(t *testing.T) { + home := t.TempDir() + r := &LiveUsageReader{SessionsRoot: filepath.Join(home, "sessions")} + sessions := []LiveUsageSession{{ID: "root", WorkingDirectory: home, Active: true, TotalTokens: 1100}} + id := "root" + if child { + id = "child" + r.files = map[string]*rolloutCursor{"child": {threadID: id, parentThreadID: "root", workingDirectory: home, lastModified: time.Now().Add(-48 * time.Hour), observedTokens: 1100}} + } + if got := r.usageCoverage(context.Background(), sessions, map[string]string{id: asyncCompactionCoverage}); !strings.Contains(got, "background compaction") { + t.Fatal("known risk missing", got) + } + // An idle, previously observed row remains visible, but loses Active once + // daemon/writer signals disappear. That is not verified feature recovery. + sessions[0].Active = false + for range 2 { + got := r.usageCoverage(context.Background(), sessions, nil) + if !strings.Contains(got, id) || !strings.Contains(got, "background compaction") { + t.Fatal("inactive/disconnected row hid cached risk", got) + } + } + if got := r.usageCoverage(context.Background(), sessions, map[string]string{id: ""}); got != "" { + t.Fatal("verified recovery did not clear inactive risk", got) + } + if got := r.usageCoverage(context.Background(), sessions, nil); got != "" { + t.Fatal("recovered risk reappeared", got) + } + }) + } +} diff --git a/internal/codex/usage_coverage_unix.go b/internal/codex/usage_coverage_unix.go new file mode 100644 index 0000000..c76115d --- /dev/null +++ b/internal/codex/usage_coverage_unix.go @@ -0,0 +1,69 @@ +//go:build unix + +package codex + +import ( + "context" + "errors" +) + +func (p *daemonStatusProvider) readUsageCoverage(ctx context.Context, ids []string) map[string]string { + result := make(map[string]string) + for _, id := range ids { + if !p.isSubscribed(id) { + continue + } + issue, err := p.threadUsageCoverage(ctx, id) + if err != nil { + issue = "effective background-compaction feature state could not be verified" + } + result[id] = issue + } + return result +} + +func (p *daemonStatusProvider) threadUsageCoverage(ctx context.Context, id string) (string, error) { + params := map[string]any{"threadId": id, "limit": 256} + seen := make(map[string]bool) + async, tokenBudget := false, false + for page := 0; page < 32; page++ { + var response struct { + Data *[]struct { + Name string + Enabled *bool + } + NextCursor *string + } + if err := p.request(ctx, "experimentalFeature/list", params, &response); err != nil { + return "", err + } + if response.Data == nil { + return "", errors.New("feature inventory omitted data") + } + for _, feature := range *response.Data { + if feature.Name != "async_compaction" && feature.Name != "token_budget" { + continue + } + if feature.Enabled == nil { + return "", errors.New("feature inventory omitted enablement") + } + if feature.Name == "async_compaction" { + async = *feature.Enabled + } else { + tokenBudget = *feature.Enabled + } + } + if response.NextCursor == nil || *response.NextCursor == "" { + if async && !tokenBudget { + return asyncCompactionCoverage, nil + } + return "", nil + } + if seen[*response.NextCursor] { + return "", errors.New("feature inventory repeated a cursor") + } + seen[*response.NextCursor] = true + params["cursor"] = *response.NextCursor + } + return "", errors.New("feature inventory exceeded pagination limit") +} diff --git a/internal/codex/usage_coverage_unix_test.go b/internal/codex/usage_coverage_unix_test.go new file mode 100644 index 0000000..0a4b495 --- /dev/null +++ b/internal/codex/usage_coverage_unix_test.go @@ -0,0 +1,39 @@ +//go:build unix + +package codex + +import ( + "context" + "strings" + "testing" +) + +func TestThreadUsageCoverageUsesEffectiveFeatureInventory(t *testing.T) { + for _, tc := range []struct { + name string + data any + next any + blocked bool + invalid bool + }{ + {name: "older inventory", data: []any{}}, + {name: "async enabled", data: []any{map[string]any{"name": "async_compaction", "enabled": true}}, blocked: true}, + {name: "disabled", data: []any{map[string]any{"name": "async_compaction", "enabled": false}}}, + {name: "token budget disables async path", data: []any{map[string]any{"name": "async_compaction", "enabled": true}, map[string]any{"name": "token_budget", "enabled": true}}}, + {name: "missing enablement", data: []any{map[string]any{"name": "async_compaction"}}, invalid: true}, + {name: "missing data", invalid: true}, + {name: "repeated cursor", data: []any{}, next: "same", invalid: true}, + } { + t.Run(tc.name, func(t *testing.T) { + p, f := newQuotaDaemon(t) + f.catalogues = map[string]map[string]any{"experimentalFeature/list": {"data": tc.data, "nextCursor": tc.next}} + if err := p.ensureConnected(context.Background()); err != nil { + t.Fatal(err) + } + issue, err := p.threadUsageCoverage(context.Background(), "one") + if (err != nil) != tc.invalid || strings.Contains(issue, "background compaction") != tc.blocked { + t.Fatal(issue, err) + } + }) + } +} diff --git a/internal/codex/usage_diagnostics.go b/internal/codex/usage_diagnostics.go index bab07d1..753e828 100644 --- a/internal/codex/usage_diagnostics.go +++ b/internal/codex/usage_diagnostics.go @@ -8,6 +8,7 @@ import ( type APIEqIssueKind string const ( + UsageIssueCoverage APIEqIssueKind = "INCOMPLETE COVERAGE" UsageIssueUnpriced APIEqIssueKind = "UNPRICED USAGE" UsageIssueMissing APIEqIssueKind = "MISSING RESPONSE USAGE" UsageIssueInconsistent APIEqIssueKind = "INCONSISTENT RESPONSE USAGE" @@ -61,6 +62,9 @@ func DiagnosticText(issue APIEqDiagnostic) string { } return value } + if issue.Kind == UsageIssueCoverage { + return string(issue.Kind) + " // " + field(issue.Reason, 240) + "\nSOURCE " + field(issue.Source, 48) + } basis := issue.ModelBasis if basis == "" { basis = "requested" diff --git a/internal/ui/model.go b/internal/ui/model.go index f79e2db..2d51b6d 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -216,6 +216,7 @@ type Model struct { quotaAPIIssues map[string]string quotaAPIAccount string quotaAPITelemetryIssue string + quotaAPICoverageIssue string } type fetchedMsg struct { @@ -1090,6 +1091,9 @@ func (m Model) Update(message tea.Msg) (tea.Model, tea.Cmd) { if observedAt.IsZero() { observedAt = time.Now() } + if message.usage.APIEqCoverageIssue != "" { + m.observeQuotaAPICoverage(message.usage.APIEqCoverageIssue) + } m.quotaGraphs.Observe(message.snapshot, message.err != nil, observedAt) var quotaStepCommand tea.Cmd if message.err == nil { @@ -2274,16 +2278,24 @@ func (m Model) fetch() tea.Cmd { message := fetchedMsg{ resetRevision: m.resetRevision, snapshot: snapshot, err: err, at: time.Now(), + usage: codex.LiveUsageSnapshot{APIEqCoverageIssue: before.APIEqCoverageIssue}, benchmarkQuotaRevision: m.benchmarkQuotaAccounting.revision, } if err == nil && m.usageFetcher != nil { after, afterErr := m.usageFetcher.FetchTokenUsage(context.Background()) after = m.benchmarkQuotaAccounting.combine(after) + if after.APIEqCoverageIssue != "" { + message.usage.APIEqCoverageIssue = after.APIEqCoverageIssue + } + coverageIssue := message.usage.APIEqCoverageIssue switch { case beforeErr != nil: message.usageErr = beforeErr case afterErr != nil: message.usageErr = afterErr + case coverageIssue != "": + message.usage = after + message.usage.APIEqCoverageIssue = coverageIssue case !quotaAPIAccountingEqual(before, after) || after.APIEqPendingCalls > 0: message.usageErr = errQuotaObservationChanged default: diff --git a/internal/ui/model_test.go b/internal/ui/model_test.go index ff728b3..2f7969d 100644 --- a/internal/ui/model_test.go +++ b/internal/ui/model_test.go @@ -22,19 +22,28 @@ type stubFetcher struct { } type quotaBracketFetcher struct { - snapshot codex.Snapshot - usages []codex.LiveUsageSnapshot - usageErr error - calls []string + snapshot codex.Snapshot + usages []codex.LiveUsageSnapshot + usageErr error + usageErrors []error + err error + calls []string } func (f *quotaBracketFetcher) Fetch(context.Context) (codex.Snapshot, error) { f.calls = append(f.calls, "quota") - return f.snapshot, nil + return f.snapshot, f.err } func (f *quotaBracketFetcher) FetchTokenUsage(context.Context) (codex.LiveUsageSnapshot, error) { f.calls = append(f.calls, "usage") + if len(f.usageErrors) > 0 { + err := f.usageErrors[0] + f.usageErrors = f.usageErrors[1:] + if err != nil { + return codex.LiveUsageSnapshot{}, err + } + } if f.usageErr != nil { return codex.LiveUsageSnapshot{}, f.usageErr } @@ -487,3 +496,97 @@ func footerButtonByID(model Model, id footerButtonID) (footerButton, bool) { } return footerButton{}, false } + +func TestQuotaFetchCoverageRecoveryCannotSpanUnobservedGap(t *testing.T) { + now := time.Now() + reset := now.Add(time.Hour).Unix() + clean := codex.LiveUsageSnapshot{APIEqUSD: 2, APIEqPricedCalls: 2} + gap := clean + gap.APIEqCoverageIssue = "SESSION child: background compaction usage is unobserved" + for _, bracket := range []struct { + name string + before, after codex.LiveUsageSnapshot + }{{"recovers during quota read", gap, clean}, {"appears during quota read", clean, gap}} { + t.Run(bracket.name, func(t *testing.T) { + f := "aBracketFetcher{snapshot: apiEqSnapshot(20, reset), usages: []codex.LiveUsageSnapshot{bracket.before, bracket.after}} + 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 { + t.Fatal("test did not establish an estimate") + } + message := m.fetch()().(fetchedMsg) + if message.usageErr != nil || message.usage.APIEqCoverageIssue == "" { + t.Fatal("quota bracket dropped a coverage gap", message.usageErr, message.usage) + } + updated, _ := m.Update(message) + m = updated.(Model) + if len(m.quotaAPIEvidence) != 0 || len(m.quotaAPIAnchors) != 0 || m.quotaAPICoverageIssue == "" { + t.Fatal("coverage gap retained previous evidence") + } + // A later clean bracket starts a new baseline even when the recovered + // totals already include substantial activity from the blocked interval. + recovered := codex.LiveUsageSnapshot{APIEqUSD: 10, APIEqPricedCalls: 3} + f.snapshot = apiEqSnapshot(25, reset) + f.usages = []codex.LiveUsageSnapshot{recovered, recovered} + updated, _ = m.Update(m.fetch()()) + m = updated.(Model) + if len(m.quotaAPIEvidence) != 0 || m.quotaAPICoverageIssue != "" { + t.Fatal("recovery learned across the gap") + } + recovered.APIEqUSD++ + recovered.APIEqPricedCalls++ + f.snapshot = apiEqSnapshot(30, reset) + 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) + } + }) + } +} + +func TestQuotaCoverageInvalidationSurvivesObservationErrorsAndBenchmarkDeferral(t *testing.T) { + for _, scenario := range []string{"after telemetry fails", "before telemetry fails", "quota fails", "benchmark active"} { + t.Run(scenario, func(t *testing.T) { + now := time.Now() + reset := now.Add(time.Hour).Unix() + clean := codex.LiveUsageSnapshot{APIEqUSD: 2, APIEqPricedCalls: 2} + gap := clean + gap.APIEqCoverageIssue = "known coverage gap" + unavailable := errors.New("observation unavailable") + f := "aBracketFetcher{snapshot: apiEqSnapshot(20, reset), usages: []codex.LiveUsageSnapshot{gap, clean}} + switch scenario { + case "after telemetry fails": + f.usageErrors = []error{nil, unavailable} + case "before telemetry fails": + f.usages = []codex.LiveUsageSnapshot{gap} + f.usageErrors = []error{unavailable, nil} + case "quota fails": + f.err = unavailable + } + 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 scenario == "benchmark active" { + m.benchmarkQuotaAccounting.active = true + } + message := m.fetch()().(fetchedMsg) + if message.usage.APIEqCoverageIssue == "" { + t.Fatal("gap was lost alongside another deferral") + } + if strings.Contains(scenario, "telemetry") && !errors.Is(message.usageErr, unavailable) { + t.Fatal("telemetry error was hidden", message.usageErr) + } + if scenario == "quota fails" && !errors.Is(message.err, unavailable) { + t.Fatal("quota error was hidden", message.err) + } + updated, _ := m.Update(message) + m = updated.(Model) + if len(m.quotaAPIEvidence) != 0 || len(m.quotaAPIAnchors) != 0 || m.quotaAPICoverageIssue == "" { + t.Fatal("deferred observation retained stale capacity evidence") + } + }) + } +} diff --git a/internal/ui/quota_api_eq.go b/internal/ui/quota_api_eq.go index 0470c7e..1b26b6a 100644 --- a/internal/ui/quota_api_eq.go +++ b/internal/ui/quota_api_eq.go @@ -22,7 +22,8 @@ const ( var errQuotaObservationChanged = fmt.Errorf("local accounting changed during quota read") func quotaAPIAccountingEqual(left, right codex.LiveUsageSnapshot) bool { - return left.APIEqUSD == right.APIEqUSD && + return left.APIEqCoverageIssue == right.APIEqCoverageIssue && + left.APIEqUSD == right.APIEqUSD && left.APIEqTierPremiumUSD == right.APIEqTierPremiumUSD && left.APIEqUnknownTierCalls == right.APIEqUnknownTierCalls && left.APIEqPricedCalls == right.APIEqPricedCalls && @@ -86,6 +87,18 @@ type quotaAPIEstimate struct { 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() @@ -101,6 +114,11 @@ func (m *Model) observeQuotaAPIEq(snapshot codex.Snapshot, usage codex.LiveUsage 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) } @@ -312,6 +330,9 @@ func trimQuotaAPISamples(samples []quotaAPISample, key string) []quotaAPISample } 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()) { @@ -404,6 +425,9 @@ 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.quotaAPITelemetryIssue != "" { if width < 44 { return "API-EQ // " + m.quotaAPITelemetryIssue @@ -534,6 +558,9 @@ 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} + } for _, meter := range m.snapshot.Meters() { if quotaAPIMeterEligible(m.snapshot, meter) { if issue := m.quotaAPIAnchors[quotaAPIKey(m.snapshot, meter)].restartIssue; issue.Kind != "" { diff --git a/internal/ui/quota_api_eq_test.go b/internal/ui/quota_api_eq_test.go index 4916e2b..5612348 100644 --- a/internal/ui/quota_api_eq_test.go +++ b/internal/ui/quota_api_eq_test.go @@ -493,3 +493,38 @@ func TestQuotaDiagnosticPreservesFooterAcrossTerminalSizes(t *testing.T) { } } } + +func TestQuotaAPICoverageGapDiscardsEvidenceAndRestartsCleanly(t *testing.T) { + now := time.Now() + reset := now.Add(time.Hour).Unix() + m := New(nil, time.Minute) + m.snapshot = apiEqSnapshot(10, reset) + 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 { + 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") { + t.Fatal("incomplete interval learned or showed old estimates") + } + if _, ok := m.quotaAPIEstimate(m.snapshot, m.snapshot.Meters()[0]); ok { + t.Fatal("stale estimate remained usable") + } + } + // Recovery creates a fresh baseline; accumulated costs from the blocked + // 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 { + 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) + } +} diff --git a/internal/web/dist/assets/index-D1LQiLkx.js b/internal/web/dist/assets/index-Bg6CQ-Bn.js similarity index 61% rename from internal/web/dist/assets/index-D1LQiLkx.js rename to internal/web/dist/assets/index-Bg6CQ-Bn.js index 3518d45..d338386 100644 --- a/internal/web/dist/assets/index-D1LQiLkx.js +++ b/internal/web/dist/assets/index-Bg6CQ-Bn.js @@ -1,5 +1,5 @@ -(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,k=1<<23,ne=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|=k,Wt(a,t)):(a.f&8388608&&(a.f^=k),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);return}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){if(St=e,e.b?.is_pending&&e.f&16777228&&!(e.f&32768)){e.b.defer_effect(e);return}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===ne)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===ne)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&&ne in e)return e[ne]}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|=k,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();return}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=()=>{if(t){_e();return}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;if(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);return}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){if(!this.has_pending_snippet()){this.parent&&this.parent.#C(e,t);return}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))}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;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);return}i.ensure(e,e&&(t=>n(t,e)))},T)}var ri=[...` -\r\f\xA0\v`];function ii(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||ri.includes(r[o-1]))&&(s===r.length||ri.includes(r[s]))?r=(o===0?``:r.substring(0,o))+r.substring(s+1):o=s}}return r===``?null:r}function ai(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 oi(e){return e[0]!==`-`||e[1]!==`-`?e.toLowerCase():e}function si(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(oi)),i&&c.push(...Object.keys(i).map(oi));var l=0,u=-1;let t=e.length;for(var d=0;d{t.every(_i)||(`__defaultValue`in e&&fi(e,!1),`__value`in e&&pi(e,e.__value))});t.observe(e,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[`value`]}),_n(()=>{t.disconnect()})}function hi(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),gi);else{var o=e.querySelector(i)??e.querySelector(`option:not([disabled])`);a=o&&gi(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(pi(e,a,i),i&&a===void 0){var s=e.querySelector(`:checked`);s!==null&&(a=gi(s),n(a))}e.__value=a,i=!1})}function gi(e){return`__value`in e?e.__value:e.value}function _i(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 vi=Symbol(`is custom element`),yi=Symbol(`is html`),bi=de?`link`:`LINK`;function xi(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 Si(e,t){var n=Ci(e);n.checked!==(n.checked=t??void 0)&&(e.checked=t)}function Q(e,t,n,r){var i=Ci(e);M&&(i[t]=e.getAttribute(t),t===`src`||t===`srcset`||t===`href`&&e.nodeName===bi)||i[t]!==(i[t]=n)&&(t===`loading`&&(e[ae]=n),n==null?e.removeAttribute(t):typeof n!=`string`&&Ti(e).has(t)?e[t]=n:e.setAttribute(t,n))}function Ci(e){return e[oe]??={[vi]:e.nodeName.includes(`-`),[yi]:e.namespaceURI===pe}}var wi=new Map;function Ti(e){var t=e.getAttribute(`is`)||e.nodeName,n=wi.get(t);if(n)return n;wi.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 Ei(e,t,n=t){var r=new WeakSet;at(e,`input`,async i=>{var a=i?e.defaultValue:e.value;if(a=ji(e)?Mi(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(ji(e)?Mi(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}ji(e)&&n===Mi(e.value)||(e.type!==`date`||n||e.value)&&n!==e.value&&(e.value=n??``)})}var Di=new Set;function Oi(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=Ai(o,e,n.checked)),i(e)},()=>i(a?[]:null)),wn(()=>{var e=r();if(M&&n.defaultChecked!==n.checked){s=!0;return}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)}),Di.has(o)||(Di.add(o),Ye(()=>{o.sort((e,t)=>e.compareDocumentPosition(t)===4?-1:1),Di.delete(o)})),Ye(()=>{if(s){var e=a?Ai(o,e,n.checked):o.find(e=>e.checked)?.__value;i(e)}})}function ki(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 Ai(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 Pi(e,t,n){var r=Ni.observe(e,()=>n(e[t]));Sn(()=>(fr(()=>n(e[t])),r))}function Fi(e,t){return e===t||e?.[ne]===t}function Ii(e=Ge(),t,n,r){var i=Ve.r,a=G;return Sn(()=>{var o,s;return wn(()=>{o=s,s=r?.()||[],fr(()=>{Fi(n(...s),e)||(t(e,...s),o&&Fi(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&&Fi(n(...s),e)&&t(null,...s)},c=r.teardown;r.teardown=()=>{o(),c?.()}}}),e}function Li(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 Ri(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(()=>{zi(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(()=>{zi(t,r),m(n.a)})}function zi(e,t){if(e.l.s)for(let t of e.l.s)K(t);t()}var Bi=!1;function Vi(e){var t=Bi;try{return Bi=!1,[e(),Bi]}finally{Bi=t}}var Hi={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===ne||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 Ui(...e){return new Proxy({props:e},Hi)}function Wi(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=ne 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]=Vi(()=>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 Gi(e){Ve===null&&De(`onMount`),ze&&Ve.l!==null?qi(Ve).m.push(e):vn(()=>{let t=fr(e);if(typeof t==`function`)return t})}function Ki(e){Ve===null&&De(`onDestroy`),Gi(()=>()=>fr(e))}function qi(e){var t=e.l;return t.u??={a:[],b:[],m:[]}}typeof window<`u`&&((window.__svelte??={}).v??=new Set).add(`5`);function Ji(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 Yi=new class{#e=L(Xi());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=Xi()}):console.warn(`[svelte-spa-router] window 'window' is not defined, skipping initiation`)}};function Xi(){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 Zi(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 Qi(e){e?window.scrollTo(e.__svelte_spa_router_scrollX||0,e.__svelte_spa_router_scrollY||0):window.scrollTo(0,0)}function $i(e,t){Ue(t,!0);let n=Wi(t,`routes`,19,()=>({})),r=Wi(t,`prefix`,3,``),i=Wi(t,`restoreScrollState`,3,!1),a=Wi(t,`onConditionsFailed`,3,()=>{}),o=Wi(t,`onRouteLoaded`,3,()=>{}),s=Wi(t,`onRouteLoading`,3,()=>{}),c=Wi(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=Ji(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=Yi.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();ni(B(n),()=>K(t),(e,t)=>{t(e,Ui({get params(){return K(f)},get onRouteEvent(){return c()}},()=>K(p)))}),Y(e,n)},a=e=>{var n=Nr();ni(B(n),()=>K(t),(e,t)=>{t(e,Ui({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 ea=[`bars`,`pace`,`pie`,`fuel`,`resets`,`thresholds`];function ta(e){return e===`zone`||e===`consumption-pace`?`pace`:typeof e==`string`&&ea.includes(e)?e:`bars`}var na=`codexometer.web.preferences.v1`,ra={statusLine:null,tab:`quota`,view:`bars`,selected:``,defaultDetail:0,layouts:[]};function ia(){try{let e=JSON.parse(localStorage.getItem(na)||`null`);return!e||typeof e!=`object`?ra:{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:ta(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 ra}}var aa=Yt(ia()),oa=Yt({open:!1}),sa=Yt({requests:0});function ca(){let e=JSON.stringify(aa);try{localStorage.setItem(na,e)}catch{}}function la(){return aa.tab===`quota`?`/quota/`+aa.view:`/`+aa.tab}function ua(e){return aa.layouts.find(t=>t.id===e)?.level??aa.defaultDetail}function da(e){aa.defaultDetail=e,aa.layouts=[]}function fa(e,t){aa.layouts=[...aa.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:``}),pa=e=>e==null?`Unavailable`:e.toLocaleString(`en-GB`),ma=e=>!e||String(e).startsWith(`0001-`)?`Not yet available`:new Date(typeof e==`number`?e*1e3:e).toLocaleString(`en-GB`),ha=`codexometer.web.session`,ga=class extends Error{},_a;async function va(e,t,n){if(!_a||!$.connected||!$.data?.control||$.data.sessionsError)throw Error(`Session controls unavailable. Check the live connection.`);return await _a(e,t,n)}function ya(){let e=new AbortController,t,n=``;_a=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 ga(`Action rejected, expired or changed. Refresh and check Codex; nothing was retried.`);return a.json()};try{n=sessionStorage.getItem(ha)||``}catch{}let r=location.hash,i=r.startsWith(`#pair=`)?r.slice(6):``;(i||!r||r===`#`)&&(history.replaceState(null,``,`/#`+la()),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(ha,n)}catch{}}catch(t){e.signal.aborted||($.error=t instanceof Error?t.message:`Pairing failed`);return}if(!n){$.error=`Open the private pairing link printed by codexometer --web in your terminal.`;return}await o()}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(ha)}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(` +(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,k=1<<23,ne=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|=k,Wt(a,t)):(a.f&8388608&&(a.f^=k),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===ne)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===ne)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&&ne in e)return e[ne]}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|=k,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;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 ri=[...` +\r\f\xA0\v`];function ii(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||ri.includes(r[o-1]))&&(s===r.length||ri.includes(r[s]))?r=(o===0?``:r.substring(0,o))+r.substring(s+1):o=s}}return r===``?null:r}function ai(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 oi(e){return e[0]!==`-`||e[1]!==`-`?e.toLowerCase():e}function si(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(oi)),i&&c.push(...Object.keys(i).map(oi));var l=0,u=-1;let t=e.length;for(var d=0;d{t.every(_i)||(`__defaultValue`in e&&fi(e,!1),`__value`in e&&pi(e,e.__value))});t.observe(e,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[`value`]}),_n(()=>{t.disconnect()})}function hi(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),gi);else{var o=e.querySelector(i)??e.querySelector(`option:not([disabled])`);a=o&&gi(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(pi(e,a,i),i&&a===void 0){var s=e.querySelector(`:checked`);s!==null&&(a=gi(s),n(a))}e.__value=a,i=!1})}function gi(e){return`__value`in e?e.__value:e.value}function _i(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 vi=Symbol(`is custom element`),yi=Symbol(`is html`),bi=de?`link`:`LINK`;function xi(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 Si(e,t){var n=Ci(e);n.checked!==(n.checked=t??void 0)&&(e.checked=t)}function Q(e,t,n,r){var i=Ci(e);M&&(i[t]=e.getAttribute(t),t===`src`||t===`srcset`||t===`href`&&e.nodeName===bi)||i[t]!==(i[t]=n)&&(t===`loading`&&(e[ae]=n),n==null?e.removeAttribute(t):typeof n!=`string`&&Ti(e).has(t)?e[t]=n:e.setAttribute(t,n))}function Ci(e){return e[oe]??={[vi]:e.nodeName.includes(`-`),[yi]:e.namespaceURI===pe}}var wi=new Map;function Ti(e){var t=e.getAttribute(`is`)||e.nodeName,n=wi.get(t);if(n)return n;wi.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 Ei(e,t,n=t){var r=new WeakSet;at(e,`input`,async i=>{var a=i?e.defaultValue:e.value;if(a=ji(e)?Mi(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(ji(e)?Mi(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}ji(e)&&n===Mi(e.value)||(e.type!==`date`||n||e.value)&&n!==e.value&&(e.value=n??``)})}var Di=new Set;function Oi(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=Ai(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)}),Di.has(o)||(Di.add(o),Ye(()=>{o.sort((e,t)=>e.compareDocumentPosition(t)===4?-1:1),Di.delete(o)})),Ye(()=>{if(s){var e=a?Ai(o,e,n.checked):o.find(e=>e.checked)?.__value;i(e)}})}function ki(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 Ai(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 Pi(e,t,n){var r=Ni.observe(e,()=>n(e[t]));Sn(()=>(fr(()=>n(e[t])),r))}function Fi(e,t){return e===t||e?.[ne]===t}function Ii(e=Ge(),t,n,r){var i=Ve.r,a=G;return Sn(()=>{var o,s;return wn(()=>{o=s,s=r?.()||[],fr(()=>{Fi(n(...s),e)||(t(e,...s),o&&Fi(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&&Fi(n(...s),e)&&t(null,...s)},c=r.teardown;r.teardown=()=>{o(),c?.()}}}),e}function Li(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 Ri(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(()=>{zi(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(()=>{zi(t,r),m(n.a)})}function zi(e,t){if(e.l.s)for(let t of e.l.s)K(t);t()}var Bi=!1;function Vi(e){var t=Bi;try{return Bi=!1,[e(),Bi]}finally{Bi=t}}var Hi={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===ne||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 Ui(...e){return new Proxy({props:e},Hi)}function Wi(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=ne 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]=Vi(()=>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 Gi(e){Ve===null&&De(`onMount`),ze&&Ve.l!==null?qi(Ve).m.push(e):vn(()=>{let t=fr(e);if(typeof t==`function`)return t})}function Ki(e){Ve===null&&De(`onDestroy`),Gi(()=>()=>fr(e))}function qi(e){var t=e.l;return t.u??={a:[],b:[],m:[]}}typeof window<`u`&&((window.__svelte??={}).v??=new Set).add(`5`);function Ji(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 Yi=new class{#e=L(Xi());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=Xi()}):console.warn(`[svelte-spa-router] window 'window' is not defined, skipping initiation`)}};function Xi(){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 Zi(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 Qi(e){e?window.scrollTo(e.__svelte_spa_router_scrollX||0,e.__svelte_spa_router_scrollY||0):window.scrollTo(0,0)}function $i(e,t){Ue(t,!0);let n=Wi(t,`routes`,19,()=>({})),r=Wi(t,`prefix`,3,``),i=Wi(t,`restoreScrollState`,3,!1),a=Wi(t,`onConditionsFailed`,3,()=>{}),o=Wi(t,`onRouteLoaded`,3,()=>{}),s=Wi(t,`onRouteLoading`,3,()=>{}),c=Wi(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=Ji(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=Yi.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();ni(B(n),()=>K(t),(e,t)=>{t(e,Ui({get params(){return K(f)},get onRouteEvent(){return c()}},()=>K(p)))}),Y(e,n)},a=e=>{var n=Nr();ni(B(n),()=>K(t),(e,t)=>{t(e,Ui({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 ea=[`bars`,`pace`,`pie`,`fuel`,`resets`,`thresholds`];function ta(e){return e===`zone`||e===`consumption-pace`?`pace`:typeof e==`string`&&ea.includes(e)?e:`bars`}var na=`codexometer.web.preferences.v1`,ra={statusLine:null,tab:`quota`,view:`bars`,selected:``,defaultDetail:0,layouts:[]};function ia(){try{let e=JSON.parse(localStorage.getItem(na)||`null`);return!e||typeof e!=`object`?ra:{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:ta(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 ra}}var aa=Yt(ia()),oa=Yt({open:!1}),sa=Yt({requests:0});function ca(){let e=JSON.stringify(aa);try{localStorage.setItem(na,e)}catch{}}function la(){return aa.tab===`quota`?`/quota/`+aa.view:`/`+aa.tab}function ua(e){return aa.layouts.find(t=>t.id===e)?.level??aa.defaultDetail}function da(e){aa.defaultDetail=e,aa.layouts=[]}function fa(e,t){aa.layouts=[...aa.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:``}),pa=e=>e==null?`Unavailable`:e.toLocaleString(`en-GB`),ma=e=>!e||String(e).startsWith(`0001-`)?`Not yet available`:new Date(typeof e==`number`?e*1e3:e).toLocaleString(`en-GB`),ha=`codexometer.web.session`,ga=class extends Error{},_a;async function va(e,t,n){if(!_a||!$.connected||!$.data?.control||$.data.sessionsError)throw Error(`Session controls unavailable. Check the live connection.`);return await _a(e,t,n)}function ya(){let e=new AbortController,t,n=``;_a=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 ga(`Action rejected, expired or changed. Refresh and check Codex; nothing was retried.`);return a.json()};try{n=sessionStorage.getItem(ha)||``}catch{}let r=location.hash,i=r.startsWith(`#pair=`)?r.slice(6):``;(i||!r||r===`#`)&&(history.replaceState(null,``,`/#`+la()),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(ha,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(ha)}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(),()=>{_a=void 0,e.abort(),clearTimeout(t),$.connected=!1}}Be();var ba=J(`

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

`),xa=J(`

`),Sa=J(`

The longest Codex quota window selects one active model profile. ASK @@ -9,7 +9,7 @@ redemption order.

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

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

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

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

`),Xa=J(`

`),Za=J(`

No quota windows reported yet.

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

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

`,1),eo=J(` `,1);function to(e,t){Ue(t,!0);let n=Wi(t,`params`,19,()=>({})),r=F(()=>ea.filter(e=>e!==`thresholds`||!!$.data?.thresholds?.length)),i=L(Yt(Date.now())),a=F(()=>K(r).includes(ta(n().view))?ta(n().view):`bars`);Gi(()=>{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(()=>{aa.view=K(a),(n().view===`zone`||n().view===`consumption-pace`)&&Zi(`/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=eo(),l=B(c);Xr(l,21,()=>K(r),Kr,(e,t)=>{var n=Fa();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=ci(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=$a(),n=B(t),r=e=>{Y(e,Ia())};Z(n,e=>{$.data.quotaError&&e(r)});var i=H(n,2),c=e=>{var t=La(),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=>{wa(e,{})},p=e=>{var t=Ba(),n=z(t),r=V(n),i=H(n,4),a=e=>{Y(e,Ra())};Z(i,e=>{$.data.credits.length||e(a)}),Xr(H(i,2),17,()=>$.data.credits,Kr,(e,t)=>{var n=za(),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 `+ma(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=Qa(),n=B(t);let r;Xr(n,21,()=>$.data.meters,Kr,(e,t)=>{let n=F(()=>o(K(t)));var r=Xa(),i=z(r),c=V(i,!0),l=H(i,2),u=z(l),d=e=>{var n=Va(),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=Va(),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=Wa(),r=z(n),i=H(z(r)),a=e=>{Y(e,Ha())},o=e=>{var n=Ua();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||[]);Pa(e,{get used(){return K(t).used},get elapsed(){return K(n)},get trail(){return K(r)},get duration(){return K(t).duration}})}},o=e=>{Y(e,Ga())};Z(i,e=>{K(n)===null?e(o,-1):e(a)}),Y(e,r)},v=e=>{var r=Ja(),i=B(r),o=z(i);let s;P(i);var c=H(i,2),l=e=>{Y(e,Ka())};Z(c,e=>{K(a)===`fuel`&&e(l)});var u=H(c,2),d=e=>{var t=qa(),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=ui(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=ui(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=Ya(),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=ci(p,1,`meter-graphic`,null,m,{"bar-graphic":K(a)===`bars`||K(a)===`fuel`}),X(b,`RESETS // ${e??``}`)},[()=>ma(K(t).reset)]),Y(e,r)}),P(n);var i=H(n,2),c=e=>{Y(e,Za())};Z(i,e=>{$.data.meters.length||e(c)}),U(()=>r=ci(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??``}`),[()=>ma($.data.quotaAt)]),Y(e,t)};Z(u,e=>{$.data&&e(d)}),Y(e,c),We()}var no=J(`
`),ro=J(`

`,1);function io(e,t){Ue(t,!0);let n=Wi(t,`values`,19,()=>[]),r=Wi(t,`label`,3,`Token activity`),i=Wi(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=ro(),c=B(s),l=V(c),u=H(c,2);Xr(u,21,()=>K(o),Kr,(e,t)=>{var n=no();let r;U((e,t)=>{Q(n,`title`,e),r=ui(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 ao(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 oo=J(` `),so=J(``),co=J(`

`),lo=J(`

`,1),uo=J(`

`),fo=J(`

Loading / sending…

`),po=J(`
`),mo=J(`

`),ho=J(`

 
`),go=J(`
`),_o=J(`
`),vo=J(`

Review expired. Press Enter to review again.

`),yo=J(`

`,1),bo=J(`

`,1),xo=J(`

No available matching commands.

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

`),wo=J(`

Review expired. Review the option again.

`),To=J(``),Eo=J(``),Do=J(``),Oo=J(``),ko=J(``),Ao=J(``),jo=J(``),Mo=J(`
`),No=J(`
`);function Po(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=so();let o;var s=z(i),c=V(s,!0),l=H(s),u=V(l),d=H(l),f=e=>{var n=oo(),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=ci(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=Wi(t,`query`,3,``),a=Wi(t,`localOnly`,3,!1),o=Wi(t,`oncopy`,3,()=>{}),s=Wi(t,`onchange`,3,()=>{}),c=Wi(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(){R(b,``),R(O,``),R(x,0),R(k,[],!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`&&!ao($.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`?sa.requests++:e===`statusline`?oa.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(``),k=L(Yt([])),ne=F(()=>K(v)?.path.startsWith(`rename/`)||K(v)?.path.startsWith(`cd/`)),re=F(()=>!!K(y)&&!K(ne)||!!K(O)||K(k).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(k,[...K(k),{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(ne)){R(y,null),await lr(),K(D)?.focus();return}let e=K(k).at(-1);e&&(R(k,K(k).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);Ki(()=>{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`,i=e.key.toLowerCase()===`c`;if((i||r||n)&&!t||![`ArrowUp`,`ArrowDown`,`Tab`,`Enter`,`Escape`].includes(e.key)&&!i&&!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&&i)return;if(n){K(re)&&oe();return}if(r){N(K(v)?.path||``,void 0,!0);return}if(i){(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}let o=K(fe)[K(T)];o&&(e.key===`Tab`?c()(o.label):K(A)?be(o):ye(o))}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);return}try{let a=await va(`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()]),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.path.startsWith(`rename/`)||a.path.startsWith(`cd/`))&&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){if(!(e.unavailable||K(g))&&!(e.next&&await m(e.next))&&!K(_)){if(K(v)?.picker){_e(K(fe).findIndex(t=>t.id===e.id)),K(D)?.focus();return}if(e.next){await ae(e.next);return}await be(e)}}async function be(e){if(K(g)||K(he)||!K(v)||e.unavailable||K(_))return;if(K(v).picker){if(K(pe)&&K(O)===e.id)return;R(O,e.id,!0)}else R(y,e,!0),lr().then(()=>K(te)?.focus());if(R(b,``),R(x,0),R(_,``),!e.action)return;let n=++w;R(g,!0);try{let r=await va(`commands`,{session:t.session,command:{mode:`prepare`,path:K(v).path,revision:K(v).revision,choice:e.id}});if(n===w){let e=Date.parse(r.expires);if(!r.confirmation||!Number.isFinite(e)||Date.now()>=e)throw Error(`Review confirmation unavailable`);R(b,r.confirmation,!0),R(x,e,!0)}}catch{n===w&&R(_,`Option changed or unavailable. Refresh before retrying.`)}finally{n===w&&R(g,!1)}}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 va(`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=No(),we=z(Ce),Te=H(we,2),Ee=e=>{var t=co(),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=Mo();let o;var s=z(i),c=e=>{var t=lo(),n=B(t),r=V(n,!0),i=V(H(n,2),!0);U(()=>{X(r,K(v)?.title||`/ COMMANDS`),X(i,K(v)?.help||`Loading live options…`)}),Y(e,t)};Z(s,e=>{K(ge)||e(c)});var l=H(s,2),u=e=>{var t=uo(),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,fo())};Z(d,e=>{K(g)&&e(f)});var p=H(d,2),m=e=>{var t=po(),r=z(t),i=z(r),o=H(i);xi(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()}),Ei(o,()=>K(C),e=>R(C,e)),Y(e,t)},h=e=>{var n=ho(),r=z(n),i=V(r,!0),a=H(r,2),o=V(a,!0),s=H(a,2),c=e=>{var n=mo(),r=z(n),i=H(r),a=e=>{Y(e,Mr(`Only this session's saved + ${$.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=>{wa(e,{})},p=e=>{var t=Ba(),n=z(t),r=V(n),i=H(n,4),a=e=>{Y(e,Ra())};Z(i,e=>{$.data.credits.length||e(a)}),Xr(H(i,2),17,()=>$.data.credits,Kr,(e,t)=>{var n=za(),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 `+ma(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=Qa(),n=B(t);let r;Xr(n,21,()=>$.data.meters,Kr,(e,t)=>{let n=F(()=>o(K(t)));var r=Xa(),i=z(r),c=V(i,!0),l=H(i,2),u=z(l),d=e=>{var n=Va(),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=Va(),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=Wa(),r=z(n),i=H(z(r)),a=e=>{Y(e,Ha())},o=e=>{var n=Ua();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||[]);Pa(e,{get used(){return K(t).used},get elapsed(){return K(n)},get trail(){return K(r)},get duration(){return K(t).duration}})}},o=e=>{Y(e,Ga())};Z(i,e=>{K(n)===null?e(o,-1):e(a)}),Y(e,r)},v=e=>{var r=Ja(),i=B(r),o=z(i);let s;P(i);var c=H(i,2),l=e=>{Y(e,Ka())};Z(c,e=>{K(a)===`fuel`&&e(l)});var u=H(c,2),d=e=>{var t=qa(),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=ui(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=ui(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=Ya(),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=ci(p,1,`meter-graphic`,null,m,{"bar-graphic":K(a)===`bars`||K(a)===`fuel`}),X(b,`RESETS // ${e??``}`)},[()=>ma(K(t).reset)]),Y(e,r)}),P(n);var i=H(n,2),c=e=>{Y(e,Za())};Z(i,e=>{$.data.meters.length||e(c)}),U(()=>r=ci(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??``}`),[()=>ma($.data.quotaAt)]),Y(e,t)};Z(u,e=>{$.data&&e(d)}),Y(e,c),We()}var no=J(`
`),ro=J(`

`,1);function io(e,t){Ue(t,!0);let n=Wi(t,`values`,19,()=>[]),r=Wi(t,`label`,3,`Token activity`),i=Wi(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=ro(),c=B(s),l=V(c),u=H(c,2);Xr(u,21,()=>K(o),Kr,(e,t)=>{var n=no();let r;U((e,t)=>{Q(n,`title`,e),r=ui(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 ao(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 oo=J(` `),so=J(``),co=J(`

`),lo=J(`

`,1),uo=J(`

`),fo=J(`

Loading / sending…

`),po=J(`
`),mo=J(`

`),ho=J(`

 
`),go=J(`
`),_o=J(`
`),vo=J(`

Review expired. Press Enter to review again.

`),yo=J(`

`,1),bo=J(`

`,1),xo=J(`

No available matching commands.

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

`),wo=J(`

Review expired. Review the option again.

`),To=J(``),Eo=J(``),Do=J(``),Oo=J(``),ko=J(``),Ao=J(``),jo=J(``),Mo=J(`
`),No=J(`
`);function Po(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=so();let o;var s=z(i),c=V(s,!0),l=H(s),u=V(l),d=H(l),f=e=>{var n=oo(),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=ci(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=Wi(t,`query`,3,``),a=Wi(t,`localOnly`,3,!1),o=Wi(t,`oncopy`,3,()=>{}),s=Wi(t,`onchange`,3,()=>{}),c=Wi(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(){R(b,``),R(O,``),R(x,0),R(k,[],!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`&&!ao($.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`?sa.requests++:e===`statusline`?oa.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(``),k=L(Yt([])),ne=F(()=>K(v)?.path.startsWith(`rename/`)||K(v)?.path.startsWith(`cd/`)),re=F(()=>!!K(y)&&!K(ne)||!!K(O)||K(k).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(k,[...K(k),{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(ne)){R(y,null),await lr(),K(D)?.focus();return}let e=K(k).at(-1);e&&(R(k,K(k).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);Ki(()=>{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`,i=e.key.toLowerCase()===`c`;if((i||r||n)&&!t||![`ArrowUp`,`ArrowDown`,`Tab`,`Enter`,`Escape`].includes(e.key)&&!i&&!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&&i)return;if(n){K(re)&&oe();return}if(r){N(K(v)?.path||``,void 0,!0);return}if(i){(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}let o=K(fe)[K(T)];o&&(e.key===`Tab`?c()(o.label):K(A)?be(o):ye(o))}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 va(`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()]),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.path.startsWith(`rename/`)||a.path.startsWith(`cd/`))&&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)),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;if(K(v).picker){if(K(pe)&&K(O)===e.id)return;R(O,e.id,!0)}else R(y,e,!0),lr().then(()=>K(te)?.focus());if(R(b,``),R(x,0),R(_,``),!e.action)return;let n=++w;R(g,!0);try{let r=await va(`commands`,{session:t.session,command:{mode:`prepare`,path:K(v).path,revision:K(v).revision,choice:e.id}});if(n===w){let e=Date.parse(r.expires);if(!r.confirmation||!Number.isFinite(e)||Date.now()>=e)throw Error(`Review confirmation unavailable`);R(b,r.confirmation,!0),R(x,e,!0)}}catch{n===w&&R(_,`Option changed or unavailable. Refresh before retrying.`)}finally{n===w&&R(g,!1)}}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 va(`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=No(),we=z(Ce),Te=H(we,2),Ee=e=>{var t=co(),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=Mo();let o;var s=z(i),c=e=>{var t=lo(),n=B(t),r=V(n,!0),i=V(H(n,2),!0);U(()=>{X(r,K(v)?.title||`/ COMMANDS`),X(i,K(v)?.help||`Loading live options…`)}),Y(e,t)};Z(s,e=>{K(ge)||e(c)});var l=H(s,2),u=e=>{var t=uo(),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,fo())};Z(d,e=>{K(g)&&e(f)});var p=H(d,2),m=e=>{var t=po(),r=z(t),i=z(r),o=H(i);xi(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()}),Ei(o,()=>K(C),e=>R(C,e)),Y(e,t)},h=e=>{var n=ho(),r=z(n),i=V(r,!0),a=H(r,2),o=V(a,!0),s=H(a,2),c=e=>{var n=mo(),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(`No change sent yet. Model, reasoning and speed settings can change token usage and cost. Changes affect subsequent turns of @@ -19,7 +19,7 @@ request.

`),qo=J(`

Session controls temporarily unavailable. Check Codex for current state.

`),Jo=J(`

Checking session controls…

`),Yo=J(`

`),Xo=J(`
About browser controls

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

`),Zo=J(` `,1),Qo=J(` `),$o=J(`Grants permission beyond this one action. Check the scope carefully.`),es=J(``),ts=J(``),ns=J(``),rs=J(``),is=J(`

Not before this time; when quota and idle allow.

`),as=J(`

`,1),os=J(`

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

`),ss=J(` `,1),cs=J(``),ls=J(``),us=J(``),ds=J(` `,1),fs=J(`
  • `),ps=J(`
    View fixed choices

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

      `),ms=J(` `,1),hs=J(`

      `),gs=J(`

      `,1),_s=J(` `,1),vs=J(``),ys=J(`

      `,1),bs=J(`

      `);function xs(e,t){Ue(t,!0);let n=[],r=Wi(t,`observedCommand`,3,``),i=Wi(t,`observedFileChanges`,19,()=>[]),a=Wi(t,`review`,3,``),o=Wi(t,`suspended`,3,!1),s=Wi(t,`openScheduler`,3,!1),c=Wi(t,`onProtectedChange`,3,e=>{}),l=Wi(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([])),k=F(()=>K(O).find(e=>e.status!==`sent`)),ne=L(void 0),re=L(void 0);function ie(e){if(K(x)){lr().then(()=>K(ne)?.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(ne)?.focus()),R(d,[e.text],!0),R(T,e.trigger===`quota`?`quota`:`at`,!0),R(p,``),R(w,``);let t=new Date(e.at);e.trigger!==`quota`&&R(D,new Date(t.getTime()-t.getTimezoneOffset()*6e4).toISOString().slice(0,16),!0)}vn(()=>{s()&&K(k)&&!K(C)&&K(u)?.id&&K(u).kind===`prompt`&&!K(u).questions?.length&&(R(C,!0),ie(K(k)))});async function ae(){if(!a())try{R(O,await va(`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 va(`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 new Date(e).toLocaleString([],{timeZoneName:`short`})}function _e(e){if(e.status!==`pending`)return e.status;if(e.trigger===`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;Gi(()=>{let e=e=>{e.ctrlKey&&e.key.toLowerCase()===`s`&&!a()&&K(k)&&(e.preventDefault(),ie(K(k)))};window.addEventListener(`keydown`,e);let n,r=setInterval(()=>{R(h,Date.now(),!0)},1e3);async function i(){await ae();try{let e=await va(`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 va(`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)}}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 va(`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 ga?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=Zo(),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=Ho(),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=zo(),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=Bo(),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=Vo();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=bs(),o=z(r),s=V(o,!0),c=H(o,2),S=e=>{var t=Uo(),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 + it changes or sends while you edit, saving is blocked.

      `),ss=J(` `,1),cs=J(``),ls=J(``),us=J(``),ds=J(` `,1),fs=J(`
    • `),ps=J(`
      View fixed choices

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

        `),ms=J(` `,1),hs=J(`

        `),gs=J(`

        `,1),_s=J(` `,1),vs=J(``),ys=J(`

        `,1),bs=J(`

        `);function xs(e,t){Ue(t,!0);let n=[],r=Wi(t,`observedCommand`,3,``),i=Wi(t,`observedFileChanges`,19,()=>[]),a=Wi(t,`review`,3,``),o=Wi(t,`suspended`,3,!1),s=Wi(t,`openScheduler`,3,!1),c=Wi(t,`onProtectedChange`,3,e=>{}),l=Wi(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([])),k=F(()=>K(O).find(e=>e.status!==`sent`)),ne=L(void 0),re=L(void 0);function ie(e){if(K(x)){lr().then(()=>K(ne)?.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(ne)?.focus()),R(d,[e.text],!0),R(T,e.trigger===`quota`?`quota`:`at`,!0),R(p,``),R(w,``);let t=new Date(e.at);e.trigger!==`quota`&&R(D,new Date(t.getTime()-t.getTimezoneOffset()*6e4).toISOString().slice(0,16),!0)}vn(()=>{s()&&K(k)&&!K(C)&&K(u)?.id&&K(u).kind===`prompt`&&!K(u).questions?.length&&(R(C,!0),ie(K(k)))});async function ae(){if(!a())try{R(O,await va(`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 va(`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 new Date(e).toLocaleString([],{timeZoneName:`short`})}function _e(e){if(e.status!==`pending`)return e.status;if(e.trigger===`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;Gi(()=>{let e=e=>{e.ctrlKey&&e.key.toLowerCase()===`s`&&!a()&&K(k)&&(e.preventDefault(),ie(K(k)))};window.addEventListener(`keydown`,e);let n,r=setInterval(()=>{R(h,Date.now(),!0)},1e3);async function i(){await ae();try{let e=await va(`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 va(`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)}}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 va(`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 ga?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=Zo(),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=Ho(),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=zo(),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=Bo(),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=Vo();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=bs(),o=z(r),s=V(o,!0),c=H(o,2),S=e=>{var t=Uo(),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=Wo();let n;var r=V(t,!0);U(()=>{n=ci(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),k=e=>{Ro(e,{get lines(){return K(u).fileChanges}})},ie=e=>{Ro(e,{get lines(){return i()}})},ae=e=>{var t=Go(),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,Ko())};Z(O,e=>{K(u)?.fileChanges?.length&&!K(A)&&!K(y)?e(k):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,Jo())},M=e=>{var t=Zo(),n=B(t),r=e=>{var t=Yo(),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,Xo())},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=ys(),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=es(),s=z(o);xi(s),s.value=s.__value=i;var c=H(s),l=H(c),u=e=>{var t=Qo(),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,$o())};Z(d,e=>{K(r).persistent&&e(p)}),P(o),U(()=>{Q(s,`name`,`decision-`+t.session+`-`+a()),X(c,` ${K(r).label??``} `)}),Oi(n,[],s,()=>K(f),e=>R(f,e)),Y(e,o)}),Y(e,r)},b=e=>{var n=Zo(),r=B(n),i=e=>{var t=ss(),n=B(t),r=H(z(n)),i=z(r),a=e=>{var t=ts();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),mi(r),P(n);var l=H(n,2),u=e=>{var t=ns(),n=H(z(t));xi(n),P(t),Ei(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=rs(),n=H(z(t));xi(n),P(t),Ei(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=as(),n=B(t),r=V(n),i=H(n,2),a=e=>{Y(e,is())};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, @@ -30,7 +30,7 @@ stale.

        `),Us=J(`

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

        `),Ws=J(` `),Gs=J(` `),Ks=J(` `),qs=J(``),Js=J(`

        Read only — reply or approve in Codex.

        `),Ys=J(`

        `),Xs=J(`QUOTA THRESHOLD // REVIEW PROFILE ↗`),Zs=J(`
        `),Qs=J(`
        `),$s=J(`

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

        `),ec=J(`

        `),tc=J(``),nc=J(`

        `),rc=J(` `),ic=J(`

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

        `),cc=J(`

        TOKEN ACTIVITY // 30 SECOND SAMPLES

        `),lc=J(`

        TOKENS

        FULL DETAIL →
        `),uc=J(`

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

        `),dc=J(`

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

        `,1),fc=J(`

        SESSION TOTALS

        `,1);function pc(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=Bs(),s=B(o),c=e=>{var n=ks(),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=As(),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=js(),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=Ms();let r;var i=V(n,!0);U(e=>{r=ci(n,1,`attention-note`,null,r,{inferred:t().status===`CHECK SESSION`}),X(i,e)},[()=>w(t())]),Y(e,n)},_=F(()=>w(t())&&(!K(a)||K(v)||t().status===`CHECK SESSION`));Z(m,e=>{K(_)&&e(h)});var y=H(m,2),b=e=>{var n=Ns(),r=V(n,!0);U(e=>X(r,e),[()=>T(t())]),Y(e,n)};Z(y,e=>{K(i)&&e(b)});var x=H(y,2),S=e=>{var n=Ps(),r=V(n,!0);U(()=>X(r,t().text||`No session context available.`)),Y(e,n)};Z(x,e=>{(t().text||!t().workingCommand)&&e(S)});var C=H(x,2),E=e=>{var n=Fs(),r=H(B(n),2),i=z(r),a=H(i),o=e=>{var n=Mr();U(()=>X(n,`// +${(t().runningCommands||0)-+(t().commandStatus===`running`)} RUNNING`)),Y(e,n)};Z(a,e=>{!K(v)&&(t().runningCommands||0)>+(t().commandStatus===`running`)&&e(o)});var s=H(a,2),c=e=>{Y(e,Mr(`+`))};Z(s,e=>{t().runningLimited&&e(c)}),P(r);var l=V(H(r,2),!0);U(e=>{X(i,`COMMAND // ${e??``} `),X(l,t().workingCommand)},[()=>K(v)?`UNKNOWN`:(t().commandStatus||`unknown`).toUpperCase()]),Y(e,n)};Z(C,e=>{t().workingCommand&&e(E)});var D=H(C,2),ee=e=>{Ro(e,{get lines(){return t().fileChanges}})},te=e=>{var n=Rs(),r=H(B(n),2),i=V(r,!0),a=H(r,2),o=e=>{var n=Is(),r=V(n,!0);U(()=>X(r,t().command)),Y(e,n)},s=e=>{Y(e,Ls())};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(D,e=>{(!K(a)||!$.data?.control)&&t().fileChanges?.length?e(ee):(!K(a)||!$.data?.control)&&(t().command||t().status===`APPROVAL NEEDED`)&&e(te,1)});var O=H(D,2),k=e=>{var n=zs(),r=V(n);U(()=>X(r,`CONTEXT SOURCE // ${(t().source||`LOCAL`)??``} // OBSERVATION`)),Y(e,n)};Z(O,e=>{K(a)||e(k)}),Y(e,o)},r=L(void 0),i=Wi(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){if(i().id&&i().id!==t&&K(u)){e.preventDefault();return}x(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){if(i().id&&i().id!==t&&K(u)){e.preventDefault();return}x(t)}vn(()=>{let e=i().id;e&&fr(()=>{aa.selected=e,fa(e,2)})});let p=F(()=>K(a).find(e=>e.id===i().id)),m=F(()=>new URLSearchParams(Yi.querystring).get(`review`)===`profile`&&K(o).some(e=>e.session===i().id&&e.pending)),h=F(()=>K(a).some(e=>e.id===aa.selected)?aa.selected:K(a)[0]?.id),v=F(()=>!$.connected||!!$.data?.sessionsError),y=F(()=>[[`OBSERVED TOKENS`,pa(K(a).reduce((e,t)=>e+t.tokens,0))],[`LISTED SESSIONS`,pa(K(a).length)],...[[`WORKING`,`WORKING`],[`AWAITING APPROVAL`,`APPROVAL NEEDED`],[`AWAITING INPUT`,`INPUT NEEDED`],[`CHECK · INFERRED`,`CHECK SESSION`]].map(([e,t])=>[e,K(v)?`—`:pa(K(a).filter(e=>e.status===t).length)])]),b=F(()=>K(a).filter(e=>[`INPUT NEEDED`,`APPROVAL NEEDED`,`CHECK SESSION`].includes(e.status)));function x(e){aa.selected=e,lr().then(()=>document.querySelector(`.session-row.selected`)?.scrollIntoView({block:`nearest`}))}function S(e,t){if(x(e),i().id){t<0&&(fa(e,2),location.hash=`/sessions`);return}let n=ua(e)+t;n>2?location.hash=`/sessions/`+encodeURIComponent(e):fa(e,n)}function C(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(),S(i().id,-1);else if([`ArrowLeft`,`ArrowRight`].includes(e.key)&&(i().id||K(h)))e.preventDefault(),S(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));x(K(a)[Math.max(0,Math.min(K(a).length-1,t+(e.key===`ArrowUp`?-1:1)))].id)}}}function w(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.`:$.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 T(e){return e.streaming&&e.contextKind===`LAST REPLY`?`REPLY // STREAMING`:e.contextKind||`LAST ACTIVITY`}var E=fc();xr(`keydown`,Qt,C);var D=B(E),ee=V(H(z(D),2));P(D);var te=H(D,2);let O;Xr(te,21,()=>K(y),Kr,(e,t)=>{var n=F(()=>_(K(t),2));let r=()=>K(n)[0],i=()=>K(n)[1];var a=Vs(),o=z(a),s=V(o,!0),c=V(H(o,2),!0);P(a),U(()=>{X(s,r()),X(c,i())}),Y(e,a)}),P(te);var k=H(te,2),ne=V(k),re=H(k,2),ie=e=>{Y(e,Hs())};Z(re,e=>{K(v)&&e(ie)});var ae=H(re,2),oe=e=>{Y(e,Us())};Z(ae,e=>{$.data?.profileError&&e(oe)});var se=H(ae,2),A=e=>{var t=qs(),n=z(t);Xr(n,17,()=>K(b),Kr,(e,t)=>{var n=Ws();let r;var i=V(n);U((e,a)=>{r=ci(n,1,`button`,null,r,{approval:K(t).status===`APPROVAL NEEDED`}),Q(n,`href`,e),X(i,`${K(t).status??``} + Codexometer.

        `),dc=J(`

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

        `,1),fc=J(`

        SESSION TOTALS

        `,1);function pc(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=Bs(),s=B(o),c=e=>{var n=ks(),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=As(),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=js(),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=Ms();let r;var i=V(n,!0);U(e=>{r=ci(n,1,`attention-note`,null,r,{inferred:t().status===`CHECK SESSION`}),X(i,e)},[()=>w(t())]),Y(e,n)},_=F(()=>w(t())&&(!K(a)||K(v)||t().status===`CHECK SESSION`));Z(m,e=>{K(_)&&e(h)});var y=H(m,2),b=e=>{var n=Ns(),r=V(n,!0);U(e=>X(r,e),[()=>T(t())]),Y(e,n)};Z(y,e=>{K(i)&&e(b)});var x=H(y,2),S=e=>{var n=Ps(),r=V(n,!0);U(()=>X(r,t().text||`No session context available.`)),Y(e,n)};Z(x,e=>{(t().text||!t().workingCommand)&&e(S)});var C=H(x,2),E=e=>{var n=Fs(),r=H(B(n),2),i=z(r),a=H(i),o=e=>{var n=Mr();U(()=>X(n,`// +${(t().runningCommands||0)-+(t().commandStatus===`running`)} RUNNING`)),Y(e,n)};Z(a,e=>{!K(v)&&(t().runningCommands||0)>+(t().commandStatus===`running`)&&e(o)});var s=H(a,2),c=e=>{Y(e,Mr(`+`))};Z(s,e=>{t().runningLimited&&e(c)}),P(r);var l=V(H(r,2),!0);U(e=>{X(i,`COMMAND // ${e??``} `),X(l,t().workingCommand)},[()=>K(v)?`UNKNOWN`:(t().commandStatus||`unknown`).toUpperCase()]),Y(e,n)};Z(C,e=>{t().workingCommand&&e(E)});var D=H(C,2),ee=e=>{Ro(e,{get lines(){return t().fileChanges}})},te=e=>{var n=Rs(),r=H(B(n),2),i=V(r,!0),a=H(r,2),o=e=>{var n=Is(),r=V(n,!0);U(()=>X(r,t().command)),Y(e,n)},s=e=>{Y(e,Ls())};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(D,e=>{(!K(a)||!$.data?.control)&&t().fileChanges?.length?e(ee):K(a)&&$.data?.control||!t().command&&t().status!==`APPROVAL NEEDED`||e(te,1)});var O=H(D,2),k=e=>{var n=zs(),r=V(n);U(()=>X(r,`CONTEXT SOURCE // ${(t().source||`LOCAL`)??``} // OBSERVATION`)),Y(e,n)};Z(O,e=>{K(a)||e(k)}),Y(e,o)},r=L(void 0),i=Wi(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():x(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():x(t)}vn(()=>{let e=i().id;e&&fr(()=>{aa.selected=e,fa(e,2)})});let p=F(()=>K(a).find(e=>e.id===i().id)),m=F(()=>new URLSearchParams(Yi.querystring).get(`review`)===`profile`&&K(o).some(e=>e.session===i().id&&e.pending)),h=F(()=>K(a).some(e=>e.id===aa.selected)?aa.selected:K(a)[0]?.id),v=F(()=>!$.connected||!!$.data?.sessionsError),y=F(()=>[[`OBSERVED TOKENS`,pa(K(a).reduce((e,t)=>e+t.tokens,0))],[`LISTED SESSIONS`,pa(K(a).length)],...[[`WORKING`,`WORKING`],[`AWAITING APPROVAL`,`APPROVAL NEEDED`],[`AWAITING INPUT`,`INPUT NEEDED`],[`CHECK · INFERRED`,`CHECK SESSION`]].map(([e,t])=>[e,K(v)?`—`:pa(K(a).filter(e=>e.status===t).length)])]),b=F(()=>K(a).filter(e=>[`INPUT NEEDED`,`APPROVAL NEEDED`,`CHECK SESSION`].includes(e.status)));function x(e){aa.selected=e,lr().then(()=>document.querySelector(`.session-row.selected`)?.scrollIntoView({block:`nearest`}))}function S(e,t){if(x(e),i().id){t<0&&(fa(e,2),location.hash=`/sessions`);return}let n=ua(e)+t;n>2?location.hash=`/sessions/`+encodeURIComponent(e):fa(e,n)}function C(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(),S(i().id,-1);else if([`ArrowLeft`,`ArrowRight`].includes(e.key)&&(i().id||K(h)))e.preventDefault(),S(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));x(K(a)[Math.max(0,Math.min(K(a).length-1,t+(e.key===`ArrowUp`?-1:1)))].id)}}}function w(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.`:$.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 T(e){return e.streaming&&e.contextKind===`LAST REPLY`?`REPLY // STREAMING`:e.contextKind||`LAST ACTIVITY`}var E=fc();xr(`keydown`,Qt,C);var D=B(E),ee=V(H(z(D),2));P(D);var te=H(D,2);let O;Xr(te,21,()=>K(y),Kr,(e,t)=>{var n=F(()=>_(K(t),2));let r=()=>K(n)[0],i=()=>K(n)[1];var a=Vs(),o=z(a),s=V(o,!0),c=V(H(o,2),!0);P(a),U(()=>{X(s,r()),X(c,i())}),Y(e,a)}),P(te);var k=H(te,2),ne=V(k),re=H(k,2),ie=e=>{Y(e,Hs())};Z(re,e=>{K(v)&&e(ie)});var ae=H(re,2),oe=e=>{Y(e,Us())};Z(ae,e=>{$.data?.profileError&&e(oe)});var se=H(ae,2),A=e=>{var t=qs(),n=z(t);Xr(n,17,()=>K(b),Kr,(e,t)=>{var n=Ws();let r;var i=V(n);U((e,a)=>{r=ci(n,1,`button`,null,r,{approval:K(t).status===`APPROVAL NEEDED`}),Q(n,`href`,e),X(i,`${K(t).status??``} ${a??``} // ${(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,()=>x(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=Gs(),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)}),Xr(H(r,2),17,()=>K(s),Kr,(e,t)=>{var n=Ks(),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)},ce=F(()=>(K(b).length||K(o).some(e=>e.pending)||K(s).length)&&!K(v));Z(se,e=>{K(ce)&&e(A)});var le=H(se,2),ue=e=>{var t=Nr(),a=B(t),s=e=>{var t=Qs(),a=z(t),s=z(a),c=z(s);let d;var f=H(c);P(s);var h=H(s,2);P(a);var g=H(a,2),_=e=>{var t=zs(),n=V(t,!0);U(()=>X(n,K(p).directory)),Y(e,t)};Z(g,e=>{K(p).name&&K(p).directory&&e(_)});var y=H(g,2),b=V(y),S=H(y,2);let C;var w=z(S),T=z(w);n(T,()=>K(p),()=>!0,()=>!0),P(w);var E=H(w,2),D=e=>{var t=Nr();Gr(B(t),()=>K(p).id,e=>{{let t=F(()=>new URLSearchParams(Yi.querystring).get(`schedule`)===`edit`),n=F(()=>K(p).fileChanges||[]);xs(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)},ee=e=>{var t=Js();Gr(H(z(t),2),()=>K(p).id,e=>{Po(e,{get session(){return K(p).id},localOnly:!0,oncopy:()=>K(r)?.copy()})}),P(t),Y(e,t)};Z(E,e=>{$.data?.control?e(D):e(ee,-1)}),P(S);var te=H(S,2);Xr(te,17,()=>K(o).filter(e=>e.session===K(p).id),e=>e.session,(e,t)=>{var n=Zs(),r=z(n),i=e=>{var n=Ys(),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=>{xs(e,{get session(){return K(p).id},review:`profile`})},s=e=>{var t=Xs();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 O=H(te,2),k=e=>{var t=Nr();Gr(B(t),()=>K(p).id,e=>{Ii(Os(e,{get session(){return K(p)},active:!0}),e=>R(r,e,!0),()=>K(r))}),Y(e,t)};Z(O,e=>{K(m)||e(k)}),Gr(H(O,2),()=>K(p).id,e=>{Es(e,{get session(){return K(p)},get stale(){return K(v)}})}),P(t),U((e,t)=>{d=ci(c,1,`lamp lit`,null,d,{working:K(p).status===`WORKING`&&!K(v)}),X(f,`${e??``} // ${(K(p).name||K(p).directory)??``}`),X(b,`${t??``} TOKENS // ${K(p).id??``} // CONTEXT SOURCE // ${(K(p).source||`LOCAL`)??``}`),C=ui(S,``,C,{display:K(m)?`none`:void 0})},[()=>K(v)?`STALE`:K(m)?`QUOTA THRESHOLD`:l(K(p)),()=>pa(K(p).tokens)]),q(`click`,h,()=>{x(i().id),fa(i().id,2)}),Y(e,t)},c=e=>{Y(e,$s())};Z(a,e=>{K(p)?e(s):e(c,-1)}),Y(e,t)},de=e=>{var t=dc(),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(()=>ua(K(t).id));var i=lc();let a;var s=z(i),c=z(s),u=e=>{var n=ec(),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,()=>x(K(t).id)),Y(e,n)};Z(c,e=>{K(t).name&&e(u)});var f=H(c,2),p=z(f),m=z(p);let g;var _=H(m,1,!0);P(p),P(f);var y=H(f,2),b=e=>{var n=tc(),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,()=>x(K(t).id)),Y(e,n)};Z(y,e=>{K(t).name||e(b)});var C=H(y,2),w=z(C);xe(),P(C);var E=H(C,2),D=e=>{var n=zs(),r=V(n,!0);U(()=>X(r,K(t).directory)),Y(e,n)};Z(E,e=>{K(t).name&&K(t).directory&&e(D)});var ee=H(E,2),te=V(ee),O=H(ee,2),k=V(O),ne=H(O,2),re=z(ne),ie=H(re,2),ae=V(ie,!0),oe=H(ie,2);P(ne);var se=H(ne,2),A=H(se,2),ce=e=>{var n=nc(),r=V(n,!0);U(()=>X(r,K(t).status===`CHECK SESSION`?`INFERRED INACTIVITY`:$.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)},le=F(()=>!K(v)&&[`APPROVAL NEEDED`,`INPUT NEEDED`,`CHECK SESSION`].includes(K(t).status));Z(A,e=>{K(le)&&e(ce)}),P(s);var ue=H(s,2),de=e=>{var r=sc(),i=z(r),a=z(i),s=V(a,!0),c=H(a,2),l=e=>{var n=rc(),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=oc(),i=B(r),a=e=>{var t=ic(),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=ac();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);Os(p,{get session(){return K(t)},get active(){return K(e)}})}P(r),U(e=>X(s,e),[()=>T(K(t))]),Y(e,r)};Z(ue,e=>{K(r)>0&&e(de)});var fe=H(ue,2),j=e=>{var n=cc(),r=H(z(n),2);{let e=F(()=>(K(t).samples||[]).map(e=>e.tokens));io(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(fe,e=>{K(r)<2&&e(j)}),P(i),U((e,n,o,s,c)=>{a=ci(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)),Q(p,`href`,e),g=ci(m,1,`lamp lit`,null,g,{working:K(t).status===`WORKING`&&!K(v)}),X(_,n),X(w,`${o??``} `),X(te,`${K(t).agents??``} LINKED AGENTS`),X(k,`ACTIVE // ${s??``}`),re.disabled=K(r)===0,Q(ie,`aria-expanded`,K(r)>0),X(ae,K(r)?`HIDE DETAIL`:`SHOW DETAIL`),Q(se,`href`,c)},[()=>`#/sessions/`+encodeURIComponent(K(t).id),()=>K(v)?`STALE`:l(K(t)),()=>pa(K(t).tokens),()=>ma(K(t).activity),()=>`#/sessions/`+encodeURIComponent(K(t).id)]),q(`click`,p,()=>x(K(t).id)),q(`click`,re,()=>S(K(t).id,-1)),q(`click`,ie,()=>{x(K(t).id),fa(K(t).id,+!K(r))}),q(`click`,oe,()=>S(K(t).id,1)),q(`click`,se,()=>x(K(t).id)),Y(e,i)});var f=H(u,2),p=e=>{Y(e,uc())};Z(f,e=>{K(a).length||e(p)}),q(`click`,s,()=>da(1)),q(`click`,c,()=>da(0)),Y(e,t)};Z(le,e=>{i().id?e(ue):e(de,-1)}),U(e=>{X(ee,`OBSERVED ${e??``}`),O=ci(te,1,`session-totals`,null,O,{stale:K(v)}),X(ne,`${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 diff --git a/internal/web/dist/index.html b/internal/web/dist/index.html index ebf4529..9845735 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/state.go b/internal/web/state.go index 222b100..bf3f7e4 100644 --- a/internal/web/state.go +++ b/internal/web/state.go @@ -285,6 +285,9 @@ func (s *store) live(l codex.LiveUsageSnapshot, err error, now time.Time) { } s.state.UsageDiagnostic = codex.DiagnosticText(latest) } + if l.APIEqCoverageIssue != "" { + s.state.UsageDiagnostic = codex.DiagnosticText(codex.APIEqDiagnostic{Kind: codex.UsageIssueCoverage, Source: "usage coverage", Reason: l.APIEqCoverageIssue}) + } if s.state.Control { s.contexts = make(map[string]codex.SessionContext, len(l.Sessions)) s.directories = make(map[string]string, len(l.Sessions)) diff --git a/internal/web/usage_diagnostics_test.go b/internal/web/usage_diagnostics_test.go index 3546eda..3f80080 100644 --- a/internal/web/usage_diagnostics_test.go +++ b/internal/web/usage_diagnostics_test.go @@ -23,3 +23,16 @@ func TestUsageDiagnosticProjectionIsBoundedSafeAndChoosesNewest(t *testing.T) { t.Fatal("new source retained an old diagnostic") } } + +func TestUsageCoverageDiagnosticOverridesStaleResponseFailures(t *testing.T) { + s := newStore() + now := time.Now() + s.live(codex.LiveUsageSnapshot{APIEqCoverageIssue: "SESSION child: background compaction usage is unobserved", APIEqDiagnostics: []codex.APIEqDiagnostic{{Kind: codex.UsageIssueUnpriced, Reason: "old failure"}}}, nil, now) + if !strings.Contains(s.state.UsageDiagnostic, "INCOMPLETE COVERAGE") || !strings.Contains(s.state.UsageDiagnostic, "child") || strings.Contains(s.state.UsageDiagnostic, "old failure") { + t.Fatal(s.state.UsageDiagnostic) + } + s.live(codex.LiveUsageSnapshot{}, nil, now) + if s.state.UsageDiagnostic != "" { + t.Fatal("coverage diagnostic did not clear") + } +} diff --git a/web/package-lock.json b/web/package-lock.json index b5a6241..1bf2dfa 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -16,7 +16,7 @@ "prettier-plugin-svelte": "4.1.1", "svelte-check": "4.7.6", "typescript": "~5.9.3", - "vite": "8.3.2" + "vite": "8.3.5" } }, "node_modules/@jridgewell/gen-mapping": { @@ -65,9 +65,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.152.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", - "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.155.0.tgz", + "integrity": "sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q==", "dev": true, "license": "MIT", "funding": { @@ -91,9 +91,9 @@ } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", - "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.15.tgz", + "integrity": "sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw==", "cpu": [ "arm" ], @@ -108,9 +108,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", - "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.15.tgz", + "integrity": "sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg==", "cpu": [ "arm64" ], @@ -125,9 +125,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", - "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.15.tgz", + "integrity": "sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA==", "cpu": [ "arm64" ], @@ -142,9 +142,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", - "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.15.tgz", + "integrity": "sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A==", "cpu": [ "x64" ], @@ -159,9 +159,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", - "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.15.tgz", + "integrity": "sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ==", "cpu": [ "x64" ], @@ -176,9 +176,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", - "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.15.tgz", + "integrity": "sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ==", "cpu": [ "arm" ], @@ -193,9 +193,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", - "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.15.tgz", + "integrity": "sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw==", "cpu": [ "arm64" ], @@ -210,9 +210,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", - "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.15.tgz", + "integrity": "sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ==", "cpu": [ "arm64" ], @@ -227,9 +227,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", - "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.15.tgz", + "integrity": "sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw==", "cpu": [ "ppc64" ], @@ -244,9 +244,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", - "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.15.tgz", + "integrity": "sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA==", "cpu": [ "s390x" ], @@ -261,9 +261,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", - "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.15.tgz", + "integrity": "sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw==", "cpu": [ "x64" ], @@ -278,9 +278,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", - "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.15.tgz", + "integrity": "sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ==", "cpu": [ "x64" ], @@ -295,9 +295,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", - "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.15.tgz", + "integrity": "sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w==", "cpu": [ "arm64" ], @@ -312,9 +312,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", - "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.15.tgz", + "integrity": "sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg==", "cpu": [ "arm64" ], @@ -329,9 +329,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", - "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.15.tgz", + "integrity": "sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ==", "cpu": [ "x64" ], @@ -831,9 +831,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.19", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", - "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -913,9 +913,9 @@ } }, "node_modules/postcss": { - "version": "8.5.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", - "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "version": "8.5.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz", + "integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==", "dev": true, "funding": [ { @@ -933,9 +933,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.18", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -995,13 +995,13 @@ } }, "node_modules/rolldown": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", - "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.15.tgz", + "integrity": "sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.152.0", + "@oxc-project/types": "=0.155.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -1011,21 +1011,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.12", - "@rolldown/binding-android-arm64": "1.2.12", - "@rolldown/binding-darwin-arm64": "1.2.12", - "@rolldown/binding-darwin-x64": "1.2.12", - "@rolldown/binding-freebsd-x64": "1.2.12", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", - "@rolldown/binding-linux-arm64-gnu": "1.2.12", - "@rolldown/binding-linux-arm64-musl": "1.2.12", - "@rolldown/binding-linux-ppc64-gnu": "1.2.12", - "@rolldown/binding-linux-s390x-gnu": "1.2.12", - "@rolldown/binding-linux-x64-gnu": "1.2.12", - "@rolldown/binding-linux-x64-musl": "1.2.12", - "@rolldown/binding-openharmony-arm64": "1.2.12", - "@rolldown/binding-win32-arm64-msvc": "1.2.12", - "@rolldown/binding-win32-x64-msvc": "1.2.12" + "@rolldown/binding-android-arm-eabi": "1.2.15", + "@rolldown/binding-android-arm64": "1.2.15", + "@rolldown/binding-darwin-arm64": "1.2.15", + "@rolldown/binding-darwin-x64": "1.2.15", + "@rolldown/binding-freebsd-x64": "1.2.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.15", + "@rolldown/binding-linux-arm64-gnu": "1.2.15", + "@rolldown/binding-linux-arm64-musl": "1.2.15", + "@rolldown/binding-linux-ppc64-gnu": "1.2.15", + "@rolldown/binding-linux-s390x-gnu": "1.2.15", + "@rolldown/binding-linux-x64-gnu": "1.2.15", + "@rolldown/binding-linux-x64-musl": "1.2.15", + "@rolldown/binding-openharmony-arm64": "1.2.15", + "@rolldown/binding-win32-arm64-msvc": "1.2.15", + "@rolldown/binding-win32-x64-msvc": "1.2.15" } }, "node_modules/sade": { @@ -1158,16 +1158,16 @@ } }, "node_modules/vite": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", - "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz", + "integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", - "postcss": "^8.5.28", - "rolldown": "~1.2.11", + "postcss": "^8.5.30", + "rolldown": "~1.2.14", "tinyglobby": "^0.2.17" }, "bin": { @@ -1184,7 +1184,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.7.1", + "@vitejs/devtools": "^0.7.1 || ^0.8.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", diff --git a/web/package.json b/web/package.json index 90e7f21..60739da 100644 --- a/web/package.json +++ b/web/package.json @@ -19,6 +19,6 @@ "prettier-plugin-svelte": "4.1.1", "svelte-check": "4.7.6", "typescript": "~5.9.3", - "vite": "8.3.2" + "vite": "8.3.5" } }