Repository navigation
Cartethyia 2.7.3
Cartethyia 2.7.3
[2.7.3] - 2026-10-30
Instalasi ulang.
2.7.3di-registri npm telah diperbarui sekali setelah rilis
pertama (tarball baru memuat perbaikan qimei36 dan glass-blur). Jika instalasi
yang sudah ada bermasalah setelahnpm update— terutama error integritas
(shasum/integritymismatch) saatnpm install— uninstall dulu lalu
install ulang:npm uninstall -g cartethyia npm install -g cartethyiaData 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_promptstable (migration0031_tenant_system_promptsfor
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/.jsonimport, 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/v1request 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 acartethyia-dashboard-buildmeta
tag; the server echoes it asX-Cartethyia-Dashboard-Buildon 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/modelsround 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, migration0030) 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
(migration0029nulls 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
carryingmax_completion_tokensechoed 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 asX-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-filteris 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_identityfor 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.0027marks the
genuine-seed carrier (locale_cfg) as persisted alongside the identity. -
Qoder identity mint pipeline.
qoder-umid.tsmints 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_identityverbatim. -
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
gainsreferences/qoder-identity-and-claims.md(mint/replay/exchange rules,
claim-safety order, the DEADpro-upgradesurface), 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 — the Gemini text SKUs
(gemini-2.5-flash,gemini-2.5-flash-thinking,gemini-3-flash,
gemini-2.5-flash-lite,gemini-3.1-flash-lite,
gemini-3.5-flash-lite,gemini-3.8-flash,gemini-4-argon,
gemini-4-carbon), the Claude SKUs (claude-sonnet-4-6,
claude-opus-4-6,claude-sonnet-5-5,claude-opus-5-5), and the
tab_flash_lite_previewlaptop-class SKU — plus the image-generation rows
gemini-3.1-flash-imageand the newgemini-3-pro-image(same limits and
capabilities as its 3.1 sibling). Rows Antigravity no longer lists
(gemini-3.1-pro,gemini-3.6-flash,gemini-3.7-flash,
gpt-oss-120b) are gone; live discovery still finds any id not seeded here. - Qoder check-in speaks the desktop identity. Campaign requests now carry
the desktop client's header set (User-Agent: Qoder,cosy-clienttype: 10,
cosy-versionfrom the shared version source); without them the server
answersshowCampaign: falseand hides every campaign. OAuth state parsing
is shared with the adapter so account-state reads cannot drift, and an OAuth
device login that cannot resolve the account's user id fails loudly instead
of storing an identity-less credential every request would 401. - Scratch/handoff documentation is now in-tree as research, not feature
guides.documentation/features/09-buddy-device-binding.mdmoved to
documentation/research/buddy-device-binding.md(unverified-historical
status);handoff.mdrecords the check-in working state, the hard-won facts,
and the parked macOS-binary item.
Fixed
- A settled relay deploy closes its dialog. Deploying a relay left the
modal open after it succeeded, so the operator stared at a form whose job
was already done (and whose token had been cleared, so a second deploy
from there was impossible anyway). A success now closes it: the relay is
registered as a pool and the list behind the dialog shows it. A failure
that invalidates the session — a revoked or mistyped Cloudflare token,
which signs the console out behind it — closes too, since retrying inside
the form can only repeat it. An ordinary failure stays open, because a
taken project name or a transient error is one edited field from
succeeding. - Glass surfaces keep their blur on a built dashboard. Every glass surface
is driven at runtime by the Customization sliders (--blur-cards,
--blur-cards-depth) throughbackdrop-filter: blur(calc(…)), which no
fixed utility can express — so those rules live in the authored sheets and
land in@layer base. Tailwind declares@layer utilitieslast, and a later
layer wins the cascade regardless of specificity, so the filter was dropped
on every real build: cards, the sidebar, and the composer rendered with no
blur at all, while theircolor-mixbackground (already!important) still
painted. That is why it read as "the transparency setting is ignored" rather
than "the blur never applied", and why it never reproduced under
bun run dev, whose CSS injection does not carry the production layer
order. The runtime blur declarations are now!importantlike the
backgrounds they sit beside, anddashboard/test/glass-cascade.test.ts
locks it: a rule that loses the cascade fails the suite. - Backup export no longer buffers a whole tenant in memory. An export read
every table into one array and stringified the document in one gulp; a
deployment whoseapi_keysheld 331k minted share children was OOM-killed
during a routine backup. Every table is now read through a keyset-paged walk
(500 rows per page on its primary key; tables without a declaredidpage on
their composite natural key; contract-bounded singletons read in one capped
statement), telemetry events page through their own cursor, and the document
is assembled incrementally — the dashboard's plain download streams the body
frame by frame, and the compressed and/admin/export-backuppaths gzip it
in a single pass.MAX_ROWS_PER_TABLEremains an explicit cap on every
table, not a size threshold, and the file a stream produces is the same
document the buffered export wrote. - Telemetry reads no longer break the PostgreSQL wire at six-figure id
sets. Family-wide share reads expanded every minted child key into one
bind parameter each (~90k params onapi_key_id IN (…)), which passed the
65,535-parameter Bind cap and ended in08P01 bind message has N parameter formats but 0 parameters— a crash systemd restarted into a loop. Every
id-set membership filter is now sent as a single= ANY($n::uuid[])
parameter (chunks of individual placeholders on SQLite), deduplicated, and
guarded at 200k distinct ids with a handledTelemetryQueryInputError
instead of an unbounded wire write. A filter is never silently skipped for
being too large. - Share family reads scale with the result, not the family. Family
statistics, shared-key summaries, per-key token sums, and share-link
recipient counts resolved the whole key family in TypeScript — enumerating
tens of thousands of child rows and building multi-megabyte cache keys on
every share-page view — which grew memory and dashboard lag the longer the
server lived. A family is now addressed by its template key and rendered as
a constant-wireEXISTSagainstapi_keys, recipient counts are one SQL
aggregate, and the stats cache key hashestemplateKeyId#memberCountrather
than every family id. - An unhandled promise rejection no longer kills the gateway. A rejected
promise nothing awaited (a sweep, a cache warm, a ballooned query) was
fatal by default; the listener now logs it and stays up instead of handing
systemd another restart. - Qoder daily check-in could answer "unavailable" for an account whose grant
was live. The campaign probe raced its own enumeration: with a bound
identity the check-in now tries it first, falls back only on its error, and
settles the winning env slot atomically — a repeat run reuses the identity
that already won instead of re-rolling through one that had been consumed.