Skip to content

Releases: risunCodex/Cartethyia

Cartethyia 2.7.3

Choose a tag to compare

@risunCodex risunCodex released this 30 Oct 12:30

Cartethyia 2.7.3

[2.7.3] - 2026-10-30

Instalasi ulang. 2.7.3 di-registri npm telah diperbarui sekali setelah rilis
pertama (tarball baru memuat perbaikan qimei36 dan glass-blur). Jika instalasi
yang sudah ada bermasalah setelah npm update — terutama error integritas
(shasum/integrity mismatch) saat npm install — uninstall dulu lalu
install ulang
:

npm uninstall -g cartethyia
npm install -g cartethyia

Data aman: konfigurasi, database, dan state tersimpan di direktori data
gateway (~/.config/cartethyia / ~/.cartethyia), bukan di dalam paket,
sehingga uninstall/reinstall tidak menghapus apa pun.

Added

  • Inject System Prompt — tenant-scoped, three precedence levels. A new
    Overview panel lets a tenant append (or override) a custom system prompt
    globally, per provider, or per exact model, resolved most-specific-wins with
    a follow-global/custom toggle on every provider. Append is idempotent and
    lands after all existing system content; override replaces only the caller's
    system content, never the provider's own mandatory identity prompt. Providers
    whose upstream validates their fixed system prompt as a channel identity
    (CodeBuddy, CodeBuddy CN, WorkBuddy) receive the tenant text wrapped in
    <cartethyia:system-prompt> markers after their identity prompt — appended
    in the same turn, which the upstream accepts. Settings persist in a dedicated
    tenant_system_prompts table (migration 0031_tenant_system_prompts for
    both Postgres and SQLite; 5 MB text cap per scope), read through the
    revision-keyed cache so edits are live on the next request, with console
    routes (GET/PUT/DELETE /system-prompt) and per-scope append/override,
    .txt/.json import, and search in the dashboard. Design record:
    documentation/proposals/inject-system-prompt.md.
  • Invoke kill switch moved into API Credentials. The tenant-wide
    "deny all keys" breaker is now a Disable/Enable Invoke button in the API
    Credentials header instead of a standalone Overview card, confirm-gated:
    disabling refuses every /v1 request with 503 while the dashboard stays
    reachable to re-enable.
  • Content-derived dashboard build ID. Every production build fingerprints
    its emitted assets and embeds the ID as a cartethyia-dashboard-build meta
    tag; the server echoes it as X-Cartethyia-Dashboard-Build on HTML
    responses. Comparing that ID (plus the content-hashed CSS URL) tells you
    exactly which build a deployment serves.
  • GitHub Copilot Auto routes from the selection itself. The /auto
    selection response names the model and its supported endpoints, so the
    adapter picks the Responses-vs-Chat wire from the selection and drops the
    follow-up /models round trip on the hot path (older responders without
    endpoints still fall back to the roster lookup).

Changed

  • Adaptive Postgres pool. The pool defaults to 8 connections with a
    30-second shrinker that trims idle connections down to a configurable floor
    (DATABASE_POOL_IDLE_FLOOR, default 2), so a burst of dashboard or proxy
    load no longer leaves a hundred idle sockets pinned forever.
  • Routing: one CallerIdentity, one RoutingDecision per candidate. The
    layered re-filtering in route planning (usage floor → slow lane → health →
    plan gate → capability, each with its own re-sort) is merged into a single
    per-candidate decision and one rank sort (fresh → slow → cooling →
    saturated). Provider rotation now only reorders within the same rank, so a
    cooling account can never float back to the front. Candidates carry the
    account's owning tenant, and admission buckets scope shared accounts
    globally while tenant-owned accounts are scoped per owner — two tenants can
    no longer drain each other's concurrency ceiling on an account only one of
    them owns.
  • Overview polish. The Compression panel's transforms render as contained
    sub-cards with active/off badges and use the shared banner primitive for the
    quality warning; the gateway-endpoint, remote-support, and status blurbs
    read like an operator wrote them.

Fixed

  • Cursor continuations survive a model switch. A continuation pinned to an
    account that no longer serves the (translated) model used to fail with a
    strict 400. Pin-empty continuations that carry complete history are now
    rebaseable: they fall through to normal routing and start a fresh Run, while
    live pending calls stay hard-pinned so nothing leaks across accounts.

  • A changed model cooldown invalidates the route snapshot. Cooldown
    overrides took effect on the next snapshot rebuild rather than the next
    request; a cooldown mutation now invalidates the snapshot immediately.

  • Share links can mint at most one child key per canonical client IP.
    Enrollment links are public and IP rotation previously let one attacker mint
    unbounded children; dedupe now keys on the canonical client address.

  • Share usage accounting survives key deletion. Revoked or deleted child
    keys kept their spend counted in family totals (template counter + telemetry
    sums), quota windows render distinct resets, and a template can cap its
    active children (max_children, migration 0030) with recipients listed
    server-side paginated/sortable and a batched bulk-revoke endpoint.

  • Share template policy is no longer duplicated into children. Children
    inherit policy at resolution time instead of carrying a copied row
    (migration 0029 nulls the duplicated columns; restore normalizes), and the
    API key list orders parents before their share children.

  • The chat wire emits only its selected token-limit field. A request
    carrying max_completion_tokens echoed that alias alongside the bounded
    max_tokens, and a strict upstream refused the double field; the alias is
    now deleted in either direction.

  • DeepSeek-style reasoning replay stays present on resold models. Models
    served through a DeepSeek-family upstream kept their reasoning replay
    marker even when the canonical surface stripped it, avoiding upstream
    rejections on follow-up turns.

  • The OAuth browser dialog no longer completes on a stale account
    snapshot.
    (pre-2.7.2 carry-over, recorded here for completeness.)

  • Buddy device binding survives account export/import. The buddy family
    (CodeBuddy, CodeBuddy CN, WorkBuddy, WorkBuddy CN) binds a per-account
    device identity (qimei36, sent as X-Device-Id-List) that defaults to a
    derivation from the internal account id. Export/import mints a new account
    id, so a re-imported account silently changed device identity and the
    upstream channel check (11128) rejected it. The export now materializes the
    resolved binding into the exported auth state whenever the account does not
    already pin one, so a re-import presents the exact same device.

  • The dashboard's glass blur now survives production builds. The standard
    backdrop-filter is declared after its -webkit- counterpart in every
    glass surface, so a compliant browser applies the standard declaration last
    instead of the prefixed one; a cascade test locks the ordering on both
    stylesheets. (This is the build-blur fix, formerly diagnosed as a cache
    issue.)

  • Qoder native machine identity, bound per account. The daily 100-credit
    campaign grant is only revealed to a native device identity (UMID), so
    pooled accounts now own one: provider_accounts.native_identity (migrations
    0026_add_qoder_native_identity for both Postgres and SQLite, DB-enforced
    one-slot-per-env uniqueness), a Drizzle-backed identity store with
    in-process allocation (qoder-native-identity-store.ts), and check-in
    behavior that probes the account's bound identity first before enumerating,
    persisting the identity that actually wins the claim. 0027 marks the
    genuine-seed carrier (locale_cfg) as persisted alongside the identity.

  • Qoder identity mint pipeline. qoder-umid.ts mints genuine device
    identities by replaying the vendor bridge (vendor/qoder-umid/umidpack/ —
    Linux ELF ×86-64/aarch64, macOS Mach-O ×86-64/arm64, Windows PE + sgsdk.dll,
    plus the 3.7 MB mini-bundle rootfs), with flock-serialized seed staging on
    WSL and a documented generated-identity fallback. scripts/tools/extract-qoder-umid.ts
    re-extracts the bundle from an official client.

  • Native identity management in the dashboard. A per-account
    Native Identity dialog (qoder/qodercn account rows): bound status and slot,
    copy/paste JSON export-import for moving an identity between installs, and
    regenerate. Console routes carry per-account auth (platform admin for shared
    accounts, tenant scope otherwise), and backup export/restore round-trips
    native_identity verbatim.

  • Qoder research record. The reverse-engineering evidence now lives in-tree:
    the daily-credit campaign recipe (research/qoder-daily-credit-campaign.md),
    the UMID bridge internals (research/qoder-umid-reverse.md), the identity
    gate post-mortem, the Pro-trial activity-claim endpoints found by unpacking
    the shipped client (research/qoder-trial-activity-endpoints.md — including
    the content-length-string signature scheme, the claim-API family, and the
    provision matrix that settles provisioning as server-side cohorting), and the
    relocated Buddy device-binding research (research/buddy-device-binding.md).
    Skills updated so future sessions inherit the proven method: provider catalog
    gains references/qoder-identity-and-claims.md (mint/replay/exchange rules,
    claim-safety order, the DEAD pro-upgrade surface), and reverse-engineering
    notes the two techniques that cracked it (literal-window grepping; capturing
    the client's own signed request by hooking its shared-process bundle, restored
    byte-for-byte afterward).

Changed

  • Antigravity's static catalog matches the roster. The offline model list
    is pruned to the ids the account actually serves — th...
Read more

Cartethyia 2.7.2

Choose a tag to compare

@risunCodex risunCodex released this 30 Oct 12:30

Cartethyia 2.7.2

[2.7.2] - 2026-10-29

Added

  • Codex CLI remote model mapping. Codex now participates in the CLI Tools
    remote-routing table like Claude Code: the Codex page gains the "CLI asks
    for → Cartethyia routes to" mapping surface, and the inbound User-Agent
    (codex-tui/…, codex-cli/…) is recognised for routing:cli_mapping.
    The Codex review slot is first-class with its real source model
    (codex-auto-review), so the Guardian approval-review model can be routed to
    a serving provider instead of answering accounts_unavailable.
  • Ollama Cloud provider catalog. A static floor for the six public models
    (gemma4:31b, gpt-oss:20b/120b, nemotron-3-nano:30b/-super/-ultra) with
    live limits from the public roster, so the provider works before discovery
    runs.

Changed

  • BYOK providers can set a route client identity. The Routing Strategy
    "Client identity" control now applies to custom (BYOK) providers too: Codex
    / Claude Code / None. The operator's explicit choice wins over the adapter's
    default, and None is honored as no identity at all.

Fixed

  • Qoder "insufficient tool messages" after cancelled/compacted parallel
    calls.
    An assistant tool_calls turn whose sibling call had no result
    used to be withheld while the surviving tool result turn was still sent,
    producing an orphan tool turn the upstream rejected
    ("Messages with role 'tool' must be a response to a preceding message with
    'tool_calls'"). The assistant tool_calls emit and the tool result turns
    now share one source of truth, so a transcript is never half-stripped.
  • Responses parallel function_call items fold into one assistant turn.
    Clients that send several function_call input items back-to-back (opencode,
    Codex) decoded into separate canonical assistant turns, so the Qoder/OpenAI
    wire saw an assistant tool_calls turn before the previous one's results —
    rejected as "An assistant message with 'tool_calls' must be followed by tool
    messages responding to each 'tool_call_id'". Consecutive tool-call turns now
    merge (a leading reasoning item included).
  • Responses additional_tools item no longer rejects Codex auto-review.
    Codex's Guardian approval reviewer declares its review tools with an
    additional_tools input item; parsing it threw
    ("Unsupported Responses input item type"), which failed the whole
    auto-review request and made every permission escalation look like an
    infrastructure error. The item is client-side metadata and is now skipped
    safely.
  • Responses "Encrypted reasoning" robust to non-string payloads. A
    reasoning item whose encrypted_content is not a string no longer throws;
    it falls back to the readable reasoning, and the encoder only emits
    encrypted_content when it is a string.
  • Trae context-overflow streams classify as 413. The upstream error code
    4026 ("context length exceeded") now maps to context_length_exceeded
    (413) instead of platform_unavailable (502).
  • OAuth browser login no longer completes on a stale account snapshot. The
    dialog used a cached account baseline from an earlier login, so a second
    login could report success before anything happened. The baseline is now
    taken only after a fresh refetch, and each attempt opens a unique popup so a
    leftover window is never reused.
  • Qoder parallel tool echoes are recovered, not leaked. When Qoder echoes
    several tool calls as text ([assistant 请求调用工具] … JSON) with
    unescaped inner quotes, the whole block used to be dropped and shown as
    visible text. Multi-call echoes now salvage into real tool calls, and the
    same recovery applies to the thinking channel (reasoning_content), so
    DeepSeek-planned parallel batches run instead of leaking.
  • Routing no longer dials a cooling account through continuation affinity.
    A multi-turn request carrying tool results was pinned to the account that
    owns the live session before health was checked, so a session whose
    account had hit quota kept being dialed and reproduced the refusal — the
    "still hit a cooled-down account" report. The pin now evaluates eligibility:
    a hard-cooling account answers 429 with the real reset, and a
    disabled/locked one answers a clear 400.
  • Qoder upstream 499 refusals cool the account as quota. Qoder closes an
    exhausted-quota stream with an nginx-style 499, which read as a malformed
    request (non-retryable, no cooldown) and kept the account in rotation. 499
    scoped to qoder now classifies as quota_exceeded with a cooldown.
  • Qoder OAuth accounts recover from a cold credential cache. The loader
    stores auth_state as JSON text, so the adapter received undefined after
    a cache eviction and refused with "missing its user identity; sign in
    again". The state parser now accepts the JSON-string form too.
  • Qoder tool-protocol echoes fail closed instead of leaking. When Qoder
    echoes a tool-call block ([assistant 请求调用工具] …) that cannot be
    recovered — malformed inner JSON, unescaped quotes that do not repair —
    the turn now fails with a 502 instead of surfacing the protocol text as
    visible content and leaving the model looking successful while nothing ran.
  • Qoder echo recovery holds long, fragmented streams. A valid echo split
    across hundreds of tiny stream deltas used to trip the 200-frame hold cap
    and was released as visible prose — the leak. The cap now counts only
    frames that add no bytes, so a growing echo is held until recovery, and
    salvaged arguments are validated (malformed arguments are rejected, not
    passed through).
  • Qoder tool-style tag echoes are recovered. The model sometimes echoes a
    tool call as an XML-like tag (<execute code="..." />) instead of a JSON
    array. These now recover into structured tool calls instead of leaking as
    visible text.
  • Qoder wrapped <tool_call> echoes are recovered. The model also wraps
    each echoed call in <tool_call>…</tool_call> blocks; the stream hold-back
    now keeps tag-style candidates (not just JSON) so a long, fragmented wrapped
    echo is recovered instead of released as visible text.
  • Qoder XML-style echoes use one generic parser. Self-closing tags
    (<execute code="…" />), paired blocks (<tool_call>…</tool_call>), and
    angle-wrapped JSON (<{…}>/<[…]>, including a double-brace artifact)
    are handled by a single parser, and the stream hold-back keeps angle-wrapped
    candidates until their closing > so long fragmented echoes recover instead
    of leaking.

Cartethyia 2.7.1

Choose a tag to compare

@risunCodex risunCodex released this 28 Oct 10:55

Cartethyia 2.7.1

[2.7.1] - 2026-10-28

Fixed

  • Qoder: index-less continuation fragments and plain-text leak markers keep
    their arguments.
    Two upstream shapes still dropped tool arguments after
    the first fix: continuation chunks that omit index entirely (no pinned id
    to join, so encoders dropped the nameless fragment), and the INTL leak
    marker [assistant 请求调用工具] … when it arrives as plain text rather than
    DSML markup (it was never buffered, so it never reached recovery). The
    translator now falls back to the most recently seen call id for index-less
    fragments (mirroring the generic chat parser), and accumulates non-DSML
    text so the leak marker is recovered at end of stream with full arguments —
    the "assistant talks, then calls a tool with {} arguments" reports.

  • Routing no longer redials an account whose cooldown deadline has elapsed
    but has not been swept yet.
    A row still in cooldown status used to read
    as healthy the moment its cooldown_until passed, so requests landing in
    the window between the deadline and the next 30s recovery sweep reproduced
    the refusal that cooled the account — logging a fresh cooldown → cooldown health row for each one (the "still hit a cooled-down account"
    report). The snapshot builder now stamps a cooldown row regardless of
    whether its deadline is expired; the sweeper remains the only exit, so the
    account is not served again until it is genuinely recovered.

  • Qoder billing-limit refusals no longer invalidate the credential. Qoder
    answers an exhausted daily billing allowance with
    {"code":"110","message":"Billing daily count exceeded"} on a 403 wrapper.
    The status-only fallback read the 403 as a credential fault, so a healthy
    account was parked as auth_invalidated — the console's "Re-login
    required" — and operators were sent to re-authenticate a credential that was
    never rejected. The 110 code and the "Billing daily count exceeded" phrase
    now classify as quota (an account cooldown), and unknown numeric provider
    codes are never treated as credential evidence — the safe unclassified
    cooldown is the default for a code we do not recognise.

  • Qoder streaming tool calls no longer lose their arguments. The upstream
    identifies one streamed call by OpenAI chunk shape — id + name in the
    first chunk, then only the wire index on every continuation fragment. The
    qoder translator synthesized a fresh call_<index> id per chunk instead of
    pinning the first id, so every continuation arrived nameless under an
    unestablished identity and was dropped by the encoders. Clients saw tool
    calls whose arguments were always {} (agent harnesses rejected every
    native bash/write/read call as "missing required field"). The translator now
    pins the first id per wire index and hands retries the same behavior as the
    generic chat parser.

  • Capability degradation no longer strips tools as collateral. When the
    only capability a route cannot serve was ordered after tools in the
    degradation chain (an image attached to a text-only model such as Mercury
    3), the surviving cumulative variant had already lost the tool catalog — the
    tool-capable model then received a transcript of [tool:<name>] text
    placeholders with no tool definitions and imitated them instead of
    structurally calling tools. Degradation now tries each single-capability
    loss against the pristine request first, so the smallest surviving
    degradation wins (e.g. drop only the image, keep tools) before the
    cumulative chain runs as fallback.

  • Trae stream refusals are classified, not blanket-routed. Upstream
    1001 "we are not able to authenticate you" now surfaces as an account-scoped
    authentication_failed (401 + credential evidence) instead of a generic 502
    platform fault, and 4008 "exceeded the quota" as quota_exceeded (429).
    Error objects carried in frame bodies (HTTP 200 with {"code":…,"message":…})
    are detected too, not only named error events — the wrong classification was
    hiding the real cause and could drag the proxy pool into the story.

  • The health classifier recognizes Trae's 1001 verdict. A numeric upstream
    code was previously excluded from the credential arm, so 1001 fell through
    to the unknown fallback that never mutates — the account stayed in rotation
    and was re-dialled on every request despite the upstream's auth verdict. Trae
    1001 is now auth_invalidated (account-scoped, routing-visible), so the
    account cools/excludes and failover moves to a sibling.

  • Trae prompt-cache estimation is re-adopted and scoped per (session,
    model).
    Trae's agent surface never reports cache usage, so repeated
    prefixes are estimated locally from per-session history; two models sharing a
    conversation id cannot cross-hit, and the assessed usage rides the terminal
    event so non-streaming telemetry records real counts.

  • Buddy desktop device-token follows the official token || error
    contract.
    The desktop client sends X-Device-Token on every realm (Intl
    included) and never omits both it and the error header: when no real token
    exists it sends X-Device-Token-Error: sdk_not_initialized. The buddy
    providers now do the same instead of dropping the header (or sending it only
    on CN).

  • A first-event timeout is a deadline, not a client cancel. The
    empty-stream watchdog used to classify a silent upstream (e.g. a provider
    holding the request in a queue) as transport_closed 499 — "cancelled by
    client" — hiding a real upstream wait behind a cancellation. It is now
    deadline_exceeded 504 with upstream origin, matching the streaming
    stall/first-chunk watchdogs.

  • Qoder recovers single-object leak echoes. The INTL leak marker could be
    followed by one JSON object ({"name":…,"arguments":…}) instead of an array;
    that shape was rejected, so the whole block was released as prose and the
    marker leaked to the client as visible text. parseLeakedToolCalls now
    normalizes a single object into a one-entry array.

Added

  • Provider Detail "Activity Matrix" usage card. A collapsible card at the
    top of the provider page condenses the last 1–366 days of usage into a
    calendar heatmap (Mon–Sun weeks, GET /console/api/providers/:providerId/usage/matrix?days=N, aggregated directly
    from telemetry_events — no migration). Summary strip totals requests,
    tokens, cached tokens, and estimated cost. Cells are tinted by the day's
    dominant model with a stable per-model color (hashed hue), hover shows a
    native day tooltip broken down per model, and an expandable "Usage by model"
    list ranks models by tokens with colored share bars.

  • Inception Labs (Mercury 3) as a bundled free-tier provider. inception
    ships with mercury-3 pinned to the free tier — one million token context,
    131072 max output, reasoning and tool calling, zero price. It appears under
    Free Limited Providers in the console. A 429 from the per-minute window
    cools the account for one minute (upstream Retry-After still wins) instead
    of the generic fifteen-minute guess, so a throttled account recovers as soon
    as the window resets.

  • A random pool-group selection strategy. Pool selection previously only
    knew the ordered scans, so a burst always hit the same first pools and
    burned them one by one while siblings sat idle — the "all pools cooling,
    fresh ones unused" pattern. random shuffles the eligible pools per request
    (still skipping cooldown/blocked pools and falling through full ones). Set
    it per tenant via the pools strategy endpoint; migration 0025 ships for
    both backends.

  • Pool cooldown and probe verdicts survive refresh and restart. A probe
    429 now writes a cooldown marker the console shows, markers persist across
    restarts, and blocked egress verdicts are restored at boot — a spent or
    retired egress is not re-picked after a restart.

  • Qoder daily check-in (streak / 100 Credits per day). Qoder accounts now
    ride the same daily check-in machinery as the Buddy family: the quota sweep
    claims the grant once per day, and a manual trigger is available through the
    account quota surface. Legacy /sash/.../daily-check-in status/claim is
    tried first; when the international realm does not expose it (404), the
    CREDITS-kind activity campaign is claimed instead. Streak days and the 100
    credit grant are reported in the sweep log.

  • OpenCode free tier aligned 1:1 with the reference bridge (v2). Flat
    /inference dispatch paths, the minimal free-tier prompt, native proxy
    egress, and pool bulk actions in the console.

  • Validated direct egress uses native fetch by default, with per-provider
    opt-in and opt-out.

  • The TuringShield SDK is bundled under vendor/turing-sdk/ (WorkBuddy
    desktop 5.8.4 extract) so buddy providers generate real X-Device-Token
    values without any environment setup — CARTETHYIA_TURING_SDK_PATH remains
    an override. Per-provider payload profiles (WorkBuddy vs CodeBuddy product
    identity on channel 400111) make tokens differ between providers, and the
    Dockerfile ships the vendor directory into both builder and runtime stages.

  • Per-account device binding (qimei36). Buddy desktop chat now sends
    X-Device-Id-List: {"qimei36":"…"} resolved per account — explicit
    auth_state.qimei36 wins, otherwise a deterministic sha256 of the account id
    (32 hex, stable across restarts, no DB write). Every pooled account presents
    a distinct device alongside the machine-bound Turing token.

  • Mistral catalog expanded to models verified live. The static roster was
    re-checked against a live key: codestral-2508/latest,
    mistral-code-latest, the ministral family, voxtral-small-2507/latest,
    ...

Read more

Cartethyia 2.7.0

Choose a tag to compare

@risunCodex risunCodex released this 27 Oct 09:27

Removed

  • The process-wide concurrency cap. Admission now governs in-flight requests alone: the tenant ceiling and each key's own limit, both set in Settings. The second ceiling was derived from the memory limit, was never a number an operator set, and was the smaller of the two — so a tenant configured to 100 was refused by a constant the Settings page never mentions. CARTETHYIA_PROCESS_CONCURRENCY_LIMIT is gone.
  • The model-abuse strike and ban layer. Ten invalid-model requests inside thirty minutes banned a caller for six hours with 403 model_abuse_banned, by a counter no setting exposes and the rate ceiling does not interact with. Removed, along with the four CARTETHYIA_MODEL_STRIKE_* / CARTETHYIA_MODEL_BAN_* knobs.

Fixed

  • The memory limit no longer pretends every host without a cgroup is a 512 MiB container; the fallback is the host's real memory.
  • Qoder tool calls carry their arguments: tool history is sent in the shape INTL reads, messages carry {role, content} only, model_config declares format: "openai", and a named tool_choice is a bare string.
  • Qoder recovers a call written into its thinking, including leftover closing tags without the DSML marker, and salvages an echo whose inner quotes were left unescaped.

Added

  • The in-flight snapshot reports oldestAgeMs, so a growing age beside a flat count shows a sweep that is not running.
  • cartethyia_ip_abuse_refusals_total records why the abuse layer refuses.

Changed

  • Tailscale requires a live serve before reporting a tunnel active, and names the address or says why there is none.

npm: npm i -g cartethyia@2.7.0

Cartethyia 2.6.7

Choose a tag to compare

@risunCodex risunCodex released this 27 Oct 03:12

Fixed

  • One stalled lease no longer stops every scheduled task. runOnce
    latched the process-wide busy gate and then awaited lease.tryAcquire
    with no bound. A lease store that never answered left busy set forever,
    and because the gate is shared across every task name rather than
    per-name, every later tick of every task returned at it: the whole
    registry stopped running. That included inflight-backstop, whose entire
    job is to clean up after something that never finishes — so one stalled
    acquisition is what let stale in-flights sit until restart.

    The acquisition is bounded at 5s and treated as a skip, which is what
    tryAcquire already documented without enforcing. Skipping releases the
    gate, and every path that latches it without running the task now goes
    through one releaseGate.

  • A banned buddy account is disabled again. The ban detector matched on
    the message, but the provider's message for a ban is the same two words it
    uses for a refused request: "request illegal". The actual verdict ("Your
    account has been restricted due to unusual activity") lives in
    displayMsg, which the message extractor never reads, so the ban was
    never detected: no policy_blocked_banned, no disable, and the account
    kept serving. The body is now read too — from upstreamBody or
    details.raw — including each language of the displayMsg map and an
    appeal-bearing actions entry.

Cartethyia 2.6.6

Choose a tag to compare

@risunCodex risunCodex released this 27 Oct 02:16

[2.6.6] - 2026-10-27

Fixed

  • Hide to tray no longer exits while the gateway is still serving. The
    tray polled /health every 10s and closed itself after 3 consecutive
    misses, with a 2s probe budget. A gateway that is answering, but slowly,
    misses every probe exactly like a gateway that is gone: under a large
    prompt or a synchronous GC, /health cannot answer inside 2s, so three
    polls in a row missed and the icon exited while the gateway was still
    serving. carte --tray looked fine only because it starts idle and never
    carries that load.

    The miss count is now a wall-clock window: a gateway that is truly gone
    stays unreachable and still closes the tray promptly, while a gateway that
    is merely busy answers often enough to reset the clock. The probe budget is
    15s — far longer than a healthy answer takes, far shorter than the poll
    interval.

Added

  • Tests for the in-flight backstop sweep, which had none. The sweep is
    the only thing that reaps a flight whose finalizer never ran. These pin
    that a flight past its deadline plus the hard grace is released, that one
    inside the window is not, that a deadline moved by a live stream is
    followed, and that reaping is idempotent.

Cartethyia 2.6.5

Choose a tag to compare

@risunCodex risunCodex released this 27 Oct 01:29

[2.6.5] - 2026-10-27

Fixed

  • A synchronous GC ran while requests were in flight. main.ts passed
    every dependency to createGatewayApp except the runtime sampler, so the
    app's bindInFlight call no-op'd on an optional dependency and the sampler
    kept its () => 0 placeholder. cartethyia_process_in_flight reported zero
    forever while proxy_in_flight — the registry the placeholder was meant to
    read — reported real flights.

    checkIdleReclaim gates on inFlightSource() > 0, so a permanently-zero
    source told the gateway nothing was in flight and let it run a synchronous
    Bun.gc(true) under load. A synchronous collection stalls concurrent
    requests: latency pinned to the request deadline and clients cancelled en
    masse with 499.

    Both classes behaved exactly as written — only the wire between them was
    missing — so it is pinned by a composition test that reads main.ts and
    requires the field.

Cartethyia 2.6.4

Choose a tag to compare

@risunCodex risunCodex released this 27 Oct 00:45

[2.6.4] - 2026-10-27

Fixed

  • A full max-inflight is no longer an account rate limit.
    provider_capacity_exhausted is raised by this gateway's own routing when
    every account serving a model is at its max-inflight — the credential is
    fine, the ceiling is what rejected the request. But it is a 429 whose text
    says "rate limit", so it matched the rate-limit arm and cooled the account
    for the full 15-minute window. Every account then read cooldown at once,
    so all of them looked busy while requests piled up. Now classified as
    capacity with zero cooldown and no account penalty.

  • A refused CONNECT tunnel is a pool failure, not an account failure.
    Proxy CONNECT failed: 403 Forbidden fell through to the unclassified
    provider arm, which cooled every account behind that pool — so one dead
    front door on one provider took unrelated providers down with it. Now
    scoped to the pool.

Changed

  • policy_blocked split into a ban and a request refusal. One category
    served two shapes needing opposite remedies:

    • 11140 whose body names a restriction on the account ("Your account has
      been restricted due to unusual activity") is now
      policy_blocked_banned. It disables immediately with no window — and no
      window is what keeps it disabled, since the recovery sweep only revives a
      disabled row whose deadline has elapsed.
    • 11-128 and safety reviews are now policy_blocked_invalid_request.
      The credential is fine, so this mutates nothing: the only correct move is
      to stop retrying verbatim.

    Told apart by the body, not the code alone, and gated on the buddy family,
    so an unrelated upstream saying "restricted" cannot disable accounts.

  • The usage floor shows inactive accounts separately. "Pooled credits"
    counted active accounts only and silently dropped the rest, so the pooled
    number looked wrong with nothing to explain it. The inactive count is now
    shown alongside.

Added

  • The accounts toolbar is flat. The bulk actions (Recover, Enable,
    Disable, Export, Delete) had lived behind an "Actions" chevron, so most of
    what the toolbar could do was invisible until accounts were selected. They
    are now persistent buttons that grey out when they do not apply, so the
    toolbar's shape never depends on what is selected.

  • A static JWT's issuer is shown on the account health modal, under
    Credential management. Read from the iss claim at registration, since the
    secret is unreadable afterwards. Only shown when the payload carried the
    claim. Accounts registered before this release show no issuer until re-added.

v2.6.3 — Buddy identity hardening and tenant-safe restores

Choose a tag to compare

@risunCodex risunCodex released this 26 Oct 23:13

Release 2.6.3

  • feat(buddy): harden CodeBuddy/WorkBuddy identity against the reference gateway
  • fix(console): remap restored rows to the restoring tenant
  • release: 2.6.3 - Buddy identity hardening and tenant-safe restores

Cartethyia 2.6.2

Choose a tag to compare

@risunCodex risunCodex released this 26 Oct 13:30

[2.6.2] - 2026-10-26

Fixed

  • The update and already-running paths can be read. Two exits happened too
    fast to be read, and both made a failed action look like a successful one.

    An update that could not install still printed "this process will exit now"
    and exited, so a version that never changed looked updated. The notice now
    names what actually blocks the install — npm replaces the package by
    renaming its directory, which Windows refuses while any process holds it,
    and the hold can outlive the process that took it — and prints the command
    to run by hand. The menu holds the notice for 30 seconds instead of
    exiting, and the installer stops cartethyia/carte first so the rename
    has a chance.

    A second instance reported "already running" and exited immediately, so a
    double-clicked shortcut or a re-run in a terminal that is already serving
    closed the window before the reason could be seen. It now waits 30 seconds
    before closing.