Commit Graph
2319 Commits
Author SHA1 Message Date
Bohdan Triapitsyn bf3186c679 fix(sync): read session ownership from the record, not store membership
A session created in a git worktree while the client was already running did
not render: the message list stayed empty while the prompt and the assistant
reply were both present in the session, visible on any fresh load. Reported as
prompting in a worktree sometimes not working.

Ownership was read from which child store holds the session. That is
containment, not ownership. A project's session list includes the sessions of
its worktrees so the sidebar can group them, so the parent repository holds
worktree sessions too, and whichever store bootstrapped first won. Captured
mid-failure, the two signals disagreed outright:

  owningDirectory  /repo                      <- parent, merely holds it
  recordDirectory  /repo/.worktrees/feature   <- the session's own directory

The parent won, so every fetch was addressed to a directory that does not own
the session, the session id resolved to undefined there, and the requests
failed as /api/session/undefined in a retry loop. The session's own record is
now believed; store membership remains the fallback for a record that carries
no directory.

This also explains why the previous commit alone was not enough: settling the
guessed directory adopted this same wrong value and then cleared the guess,
which prevented any later correction.

Verified against the reproduction rather than by reasoning. Before: three of
four runs never rendered. After, on a clean build with the instrumentation
removed: three of three rendered the reply live, each routed to its own
worktree. Tests cover ownership disagreeing with containment, plus both
directions of the guess promotion.
2026-08-04 01:27:18 +03:00
Bohdan Triapitsyn a44d291cb5 fix(sync): settle a guessed session directory once its owner is known
Selecting a session whose directory this client has not indexed yet routes it
through the active directory. That is a deliberate, documented guess: it keeps
routing usable while the owning store bootstraps, and it is excluded from both
the resolver and persistence.

Nothing settled the guess afterwards. `setSessionDirectory` performs exactly
that promotion, but only confirmed destinations call it — a completed move or a
worktree this client created. A session whose directory the client learned about
later, such as one in a worktree created outside this client, kept the guess
forever: every message fetch was addressed to the parent repository, which does
not own the session.

Captured for such a session before this change, with the session already
indexed and its owning store known:

  routedDirectory          .../worktree/feature
  currentSessionDirectory  /repo            <- guess, never settled
  opencodeClientDirectory  /repo
  conflict                 selected -> /repo

and after:

  routedDirectory          .../worktree/feature
  currentSessionDirectory  .../worktree/feature
  opencodeClientDirectory  .../worktree/feature
  conflict                 null

Directory bootstrap completion is the moment the authoritative directory first
becomes readable, so the promotion runs there. It only ever promotes a guess:
a confirmed selection and a selection that has since moved on are both left
alone, and tests cover both directions.

This removes a real routing split-brain. It does not by itself fix the reported
symptom of a session created mid-session never rendering; that remains open.
2026-08-04 00:42:34 +03:00
Bohdan Triapitsyn 4773db83c5 perf: fix directory cache thrashing and runtime-key derivation, add unattended profiling harness #2598 2026-08-04 00:05:40 +03:00
Bohdan Triapitsyn 12a7d83dd1 Merge remote-tracking branch 'origin/main' into performance-improvements 2026-08-03 23:40:41 +03:00
Bohdan Triapitsyn 56f2b972f0 chore(deps): drop better-sqlite3 and its desktop packaging support
The SQLite write into OpenCode's database was the only consumer of
better-sqlite3 in the repository. Everything that existed to ship its native
binary went with it:

- the dependency in @openchamber/web and @openchamber/electron
- the afterPack hook staging better_sqlite3.node into app.asar.unpacked
- a dedicated @electron/rebuild pass (onlyModules) and its binary assertion,
  so desktop packaging now runs one native rebuild instead of two
- the bundler external entry and the AppImage required-native-module check

Desktop packaging, the AppImage verification tests, and the extension bundle
were re-validated after a clean reinstall, so no stale module could satisfy a
missed import.
2026-08-03 23:38:01 +03:00
Bohdan Triapitsyn 4c0fc25ac8 fix(worktree): stop writing worktree registration into OpenCode's storage
Creating a worktree wrote the new directory straight into OpenCode's own
project storage: the web server updated `storage/project/<id>.json` and ran an
`UPDATE project SET sandboxes` against `opencode.db` through better-sqlite3,
and the VS Code extension wrote the same JSON.

Both wrote behind the back of a running OpenCode process. OpenCode registers a
sandbox through `project.addSandbox`, which emits a project-updated event; a
direct row write emits nothing, so a worktree created while OpenCode was
running stayed unknown to it until a restart. The SQLite write also opened a
database file owned by another live process. The VS Code write was inert on top
of that: OpenCode v2 reads sandboxes from the database, not from that JSON.

Registration is not ours to perform. OpenCode records a worktree as a sandbox
itself when an instance boots for that directory, and filters entries whose
directory no longer exists when reading them back, so removal needs no
counterpart either. The only consumer on our side, the project seed in
sync/bootstrap.ts, already falls back to `project.current()` when the seed is
absent; the worktree list itself comes from git, not from sandboxes.

Reported symptom this targets: a worktree created after `openchamber restart`
never answers prompts, and restarting OpenChamber makes it work. Not reproduced
locally, so this is not confirmed as the cause.
2026-08-03 23:37:53 +03:00
Bohdan Triapitsyn 237cae16b3 fix: stop the composer re-sending a queued message already in flight
A queued message is removed from the queue only after its send resolves,
so between dispatch and resolution it stays visible to every reader — and
a composer submit merges the whole queue into its own send. Over a relay
that window is seconds, long enough to deliver the same message twice.

The queue now tracks which entries are awaiting the server. Dispatchers
skip them, clearQueue retains them so the pending send can still remove
or restore its own entry, and the flag is not persisted because a restart
has no in-flight sends.
2026-08-03 23:14:14 +03:00
Bohdan Triapitsyn fe38f7a56b fix: treat lost relay sends as ambiguous instead of failed
A prompt whose response is lost after the request left the client may
already be running server-side. The relay tunnel reported those failures
as plain text errors ("stream aborted by host", "relay keepalive
timeout"), which matched none of the patterns in isAmbiguousSendFailure,
so an accepted prompt was rolled back and the message queue re-sent it —
two independent AI responses for one user message (#2425). Direct
connections never hit the path.

Transports now tag dispatched-but-unconfirmed failures and the classifier
reads the tag before falling back to status/text heuristics. Confirmation
waits for the connection to actually return (bounded) and retries with
backoff instead of two attempts 150ms apart over the just-broken tunnel.
2026-08-03 23:09:43 +03:00
Bohdan Triapitsyn e53a8a52cf perf(tooling): avoid a second large-array spread in trace summarising
The longest-task calculation spread every recorded task into Math.max, which
overflowed the call stack on traces carrying hundreds of thousands of tasks —
the same failure already fixed for collecting trace events.
2026-08-03 19:14:45 +03:00
Bohdan Triapitsyn 51d814307f perf(tooling): measure animations in context and at document scale
Adds fixture variants that keep an identical transform animation and vary only
its surroundings — inside a button, under a filtered, clipped, blurred,
transformed or faded ancestor — plus the repository's own spinner overrides
isolated piece by piece. Adds --filler, which pads the page with static
elements, because a variant that costs nothing on a small page is not proven
free in a real document.

All of them measure zero style recalculations per second, including at 15,000
filler elements, which rules out ancestor context, the custom keyframes,
transform-box and document size as explanations for the cost the same spinner
shows inside the application.
2026-08-03 18:50:06 +03:00
Bohdan Triapitsyn e4fddabb19 fix(quota): normalize DeepSeek timeout errors 2026-08-03 18:42:31 +03:00
Bohdan Triapitsyn ca31157584 perf(tooling): add an animation cost profiler and document the harness
Adds `bun run profile:animation`: it serves an isolated fixture and measures
each animation variant directly, so comparing techniques takes seconds instead
of an application rebuild plus a streamed response.

The result is unambiguous and does not vary with element count, measured from 1
to 32: transform, opacity and filter cost zero extra style recalculations, while
the individual rotate property, background-position, border-color and box-shadow
each recalculate style 60 times a second, and geometry properties add layout on
top. Notably `rotate: 360deg` is not a cheap synonym for
`transform: rotate(360deg)`, and will-change, wrapper elements, containment and
stepped timing do not make a non-composited property cheap.

`scripts/perf/DOCUMENTATION.md` documents all four capture commands, how to
stand up a production build to measure against, how to read the artifacts, the
validity guarantees the scripts enforce, and the methodology rules, so this can
be handed to an agent as the entry point for measuring performance. It is linked
from the root guide's documentation anchors.

The theme skill gains an animation contract carrying the measured table, and the
performance skill points at the tooling documentation.
2026-08-03 18:38:58 +03:00
Bohdan Triapitsyn 1cc5cfedbb feat: add DeepSeek quota provider #2594 2026-08-03 18:37:31 +03:00
Bohdan Triapitsyn 632fc09e18 perf(tooling): reach a fully populated sidebar in both profilers
Idle and streaming cost both depend on how much of the sidebar is mounted, so
the scenario setup is now shared. --expand-projects seeds the persisted collapse
state; --expand-sessions clicks every "Show more sessions" control, which
cannot be seeded because pagination is component state. Both run before the
measured window, so it stays input-free. Session expansion must run after the
sidebar has populated, not straight after the load event, or the controls do
not exist yet.

Also replaces a spread push over collected trace events, which overflowed the
call stack once a populated sidebar produced chunks of hundreds of thousands of
events, and the equivalent spread in the heap-maximum calculation.
2026-08-03 18:01:52 +03:00
Bohdan Triapitsyn b2cac4d242 chore: ignore the whole profiling artifact directory
Captures are named per investigation, so listing individual prefixes let new
run names leak into the working tree.
2026-08-03 17:42:22 +03:00
Bohdan Triapitsyn 7a2155ad7a perf(tooling): allow viewing one session while another streams
A background session must not make the session on screen expensive. The
streaming profiler can now display a different session than the one it prompts,
which measures exactly that. The rendered-stream validity check is skipped in
this mode, because rendering nothing is the expected result.
2026-08-03 17:11:49 +03:00
Bohdan Triapitsyn d3576117da docs(skills): record the measurement traps this investigation hit
Every wrong turn in this work came from trusting a number whose validity had
not been established: a throttled renderer reporting zero rendering work, a
trace category that was never enabled reporting zero long tasks, a scenario
that rendered nothing reporting a perfectly quiet profile, and an 'after'
measured without a matching 'before', which made a no-op change look like a
fix.

The performance skill now puts measurement validity ahead of measurement,
requires a baseline from the unchanged build through the identical scenario,
directs native work to the timeline trace rather than the sampler, requires
unvalidated changes to be reverted and recorded as rejected, and says when to
stop optimising a path that is already inside budget. It also points at the
repository's capture commands.

The sync skill gains the invariant behind the cache-thrash loop: an entry
acquired during render but protected only after commit is unprotected for the
whole render pass, capacity should be a soft target, eviction must not run on
the acquisition path, and raising a limit relocates a cliff instead of removing
it.
2026-08-03 17:05:56 +03:00
Serhii Dziupin 166b89d8db Merge pull request #2596 from openchamber/fix-Kimi-for-Coding-usage
fix: Kimi for Coding usage showing 0% despite full consumption
2026-08-03 17:02:43 +03:00
Bohdan Triapitsyn 262b1eb18a perf(tooling): report which animations run during a streaming capture
Compositing shows up in a trace as Layerize, Commit and PrePaint with no
indication of what caused it. The streaming profiler now snapshots
document.getAnimations() mid-capture and reports the running animations by
keyframe and target, which names the elements keeping the compositor busy
instead of leaving the trace to be guessed at.
2026-08-03 16:43:19 +03:00
Serhii Dziupin 635a70b24f fix: compute Kimi quota usage from used or remaining field 2026-08-03 16:39:14 +03:00
Serhii Dziupin 5414bad539 Merge pull request #2589 from openchamber/feat/opencode-argv-0-path-a7e7
fix(desktop): strip AppImage ARGV0 leak corrupting zsh argv[0] (#2588)
2026-08-03 16:37:54 +03:00
Bohdan Triapitsyn ea9bb52fe7 fix(sync): stop directory cache thrashing when a project is expanded
Expanding a project with more worktrees and sessions than MAX_DIR_STORES put
the sidebar into an endless request loop (#1472).

Every sidebar row calls ensureChild during render, but the pin that protects
the directory is only taken in an effect after commit. ensureChild marked the
directory and ran eviction synchronously, so directories that were actively
rendering looked unpinned and were disposed. The next render recreated them
with a loading status, which issued another bootstrap request, and the cycle
repeated for as long as the project stayed expanded.

Raising the limit only moves the cliff, so the limit is now a soft target
instead: a directory touched within a grace window is never an overflow
victim. A burst of live directories overflows the cache briefly rather than
thrashing, while idle-time eviction still bounds it. Eviction is also coalesced
into one deferred pass per tick, so a render that mounts many rows no longer
sorts and scans every directory once per row, and a whole commit's pin effects
settle before anything is considered for disposal. Releasing the final consumer
stays synchronous, since that is an explicit lifecycle edge.

The idle profiler gains --expand-projects to reach this state.

Not yet verified end to end: reproducing the loop needs many worktrees under
one project, which this development environment does not have.
2026-08-03 16:28:44 +03:00
Bohdan Triapitsyn fe9e2471cb perf(tooling): break streaming time down by timeline trace event
A CPU sampling profile attributes native work to `(program)`, which during
streaming accounted for three quarters of all busy time and said nothing about
where it went. The timeline trace names that work, so the streaming report now
lists total and maximum time per trace event, skipping container events whose
duration already includes the work below them.
2026-08-03 16:17:51 +03:00
Bohdan Triapitsyn 107fe45248 perf(runtime): cache the derived runtime key
`getRuntimeKey` keys caches, stores, and persisted state across the whole UI,
so it runs on store reads, event handling, and render paths. Until the runtime
endpoint is explicitly initialised, every call re-derived the key by trimming
two injected globals and constructing three URL objects.

In a streaming capture this made `readInjectedLocalOrigin` the single most
expensive application function: 315 ms of self time, 12% of all main-thread
busy time. After the change it does not appear in the profile at all, and the
same capture went from two long tasks to none, with the longest task dropping
from 210 ms to 47 ms.

The key depends only on the active API base URL and two injected globals, and
`switchRuntimeEndpoint` writes the injected API base URL at runtime, so the
cache is validated against the raw untrimmed values rather than memoised
outright. That comparison allocates nothing and still recomputes as soon as any
input changes. Tests cover both directions, including an operation-count
assertion that repeated calls construct no URLs.

The streaming profiler also reports output-normalised metrics, because response
length varies between runs and makes per-second totals incomparable.
2026-08-03 15:31:22 +03:00
Bohdan Triapitsyn 0d603649dc perf(tooling): add automated streaming profiler
Adds `bun run profile:session`: creates a session, opens it in a real
browser, dispatches a prompt through the supported `openchamber session` CLI,
and records until the session reports itself idle. No input is synthesised, so
everything captured is the app reacting to its own event stream.

Streaming is judged by responsiveness rather than totals, so the report leads
with the long-task distribution, style recalculation and layout rates, frame
production, and the application's own stream counters.

Two failure modes are detected rather than reported as clean results. A session
belonging to a directory the browser is not viewing renders nothing and
produces a perfectly quiet profile, so the run verifies both new message
elements in the DOM and message-list render counters. And `RunTask` is only
emitted under the disabled-by-default timeline category, so a capture without
it reports zero long tasks; the missing-task case is now called out instead of
being shown as zero.

Metric helpers are shared with the idle profiler.
2026-08-03 15:24:30 +03:00
Bohdan Triapitsyn 74b2e0a7f1 perf(tooling): add scenario controls to the idle profiler
Idle cost depends on which surfaces are mounted, so the profiler needs to
reach those states without a human driving the UI.

`--panel <mode>` opens the context panel by seeding the persisted store the
app reads on boot, using the same tab identity rules as `useUIStore`.
`--then-tab <name>` navigates through the router after settling, which leaves
already-mounted surfaces mounted and measures what a screen keeps doing once
the user has moved on. Both drive real application state instead of
synthesising clicks, so the recorded window stays free of input-driven work.
2026-08-03 15:04:49 +03:00
Serhii Dziupin 9289dea4a0 Merge pull request #2586 from openchamber/feat/skill-renaming-content-preservation-c1d5
fix(skills): preserve SKILL.md content when renaming
2026-08-03 15:04:48 +03:00
Howon Lee 2dd3bbfe8e feat: add DeepSeek quota provider 2026-08-03 20:46:32 +09:00
Bohdan Triapitsyn 255c738b5e perf(tooling): add automated idle profiling harness
Adds `bun run profile:idle`: a fully unattended capture of what OpenChamber
does while nobody interacts with it. It reports main-thread busy time, style
recalculation and layout rates, DOM node and listener growth, heap trajectory,
a CPU sampling profile, and per-call-site attribution of timer, animation
frame, and observer work.

Chrome throttles timers and stops producing frames for occluded or backgrounded
windows, which silently reports an idle-looking renderer regardless of what the
page schedules. Launch flags now disable that throttling, and every run measures
frame liveness so a throttled capture is reported as a warning rather than as a
clean result.

CDP launch and client code is shared with the existing browser profiler.
2026-08-03 14:20:27 +03:00
Cursor AgentandSerhii Dziupin 0d24d0a167 fix(skills): repair renameSkill directory resolution after merge
Use getRequestDirectory and x-opencode-directory like the other skill
mutations, and pin renamable list/store mapping with focused tests.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-03 11:05:27 +00:00
Bohdan Triapitsyn e0bd787468 docs: changelog entries for the upcoming release 2026-08-03 13:55:51 +03:00
Bohdan Triapitsyn ce09ca6190 fix(walkthrough): offer German, and catch the next locale that is only half added
German was added to the interface but not to the walkthrough's own language
list, and nothing failed: the picker offered Deutsch because it is built from
the interface locales, the server resolved the tag to English, and a German
reader paid for a walkthrough written in English while the picker still said
Deutsch.

The two lists cannot be one — the server cannot import from packages/ui — so
a test reads i18n/runtime.ts and compares them, in both directions and through
normalizeLanguage. Drift this quiet needs a test rather than vigilance.
2026-08-03 13:55:51 +03:00
Bohdan Triapitsyn 2dca614849 fix(opencode): preserve managed process liveness 2026-08-03 13:24:01 +03:00
Bohdan Triapitsyn 7d311d99c9 feat: add custom/other OpenAI-compatible LLM providers #2571 2026-08-03 13:21:45 +03:00
Bohdan Triapitsyn 2a79c07d8b fix(chat): bound terminal output expansion 2026-08-03 12:56:19 +03:00
Bohdan Triapitsyn 753e4cccaa fix(ui): prevent status row controls from overlapping on narrow mobile #2590 2026-08-03 12:56:05 +03:00
Cursor AgentandSerhii Dziupin 5b9a8c4bef merge(main): resolve skills.test.js import conflict
Keep both discoverSkills from main and renameSkill from this branch.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-03 09:55:56 +00:00
RyderAsking b9447f0ffb fix(ui): preserve narrow desktop status-row behavior
Keep the active todo text hidden below 38rem, as before, while hiding only the changed-files secondary label below 30rem. This fixes the mobile collision without reintroducing the documented narrow desktop overlap.
2026-08-03 09:55:55 +00:00
Cursor AgentandSerhii Dziupin 47b441d719 merge(main): resolve terminal runtime.test.js ARGV0 vs DA query
Keep ARGV0/env-u assertions from this branch and the DA startup-reply
expectations from main's terminal PTY-before-viewport fix.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-03 09:54:22 +00:00
Bohdan Triapitsyn 2c52240f8e fix(sync): route sessions by server-confirmed directory, unstick queued sends
Session directory resolution had no precedence contract: the selection-time
directory short-circuited every lookup, and a persisted runtime value was
consulted before the authoritative record. A worktree session selected before
its directory store bootstrapped kept the active-directory fallback, and that
guess was persisted, so it survived reloads and restarts.

Directory resolution now lives in one module and orders sources by whether the
server confirmed the path, not by whether the value is local or synced:
authoritative (the child store that holds the session) > server-confirmed
selection > worktree attachment/metadata (the requested path, pre-canonical) >
remembered. A guessed selection is no longer persisted, remembered, or ranked.
Chips read the same resolution the composer used, so queue keys cannot diverge.

Queued auto-send could strand an item indefinitely: backoff, missing send
configuration, and the recent-abort window all returned without scheduling a
wake-up, so the queue only retried when an unrelated status or directory change
re-ran the effect. A retry scheduler now wakes it at the earliest known time.

A rejected send rolls the optimistic message back while the composer stays
silent for transport failures, which makes it indistinguishable from nothing
happening. Failures are now recorded to a bounded in-memory log surfaced in the
About diagnostics report, alongside a directory-resolution breakdown, plus
__opencodeDebug.diagnoseSessionDirectory() and getRecentSendFailures().

Prompted by a report of worktree prompting silently failing. That failure was
not reproduced locally, so the diagnostics are what will identify it.
2026-08-03 12:51:12 +03:00
Bohdan Triapitsyn c5bf04b53a fix(chat): normalize bash output by stripping ANSI sequences and applying terminal control codes #2554 2026-08-03 12:50:56 +03:00
Serhii Dziupin 94c9ac3153 Merge pull request #2592 from openchamber/terminal-open-debug
fix(terminal): start PTY before viewport mounts without dropping output or replies
2026-08-03 12:50:19 +03:00
Serhii Dziupin 88937ade72 fix(terminal): start PTY before viewport mounts without dropping output or startup replies
Terminal creation no longer waits for the Ghostty viewport to report its
size: it starts the PTY immediately with a container/font-derived
provisional size (falling back to 80x24), then resizes once the real
viewport dimensions are known, with a dedupe guard while sizing settles.

Starting the shell earlier means it can emit device/theme queries before
a browser terminal is attached to answer them, so the server now answers
primary device attribute queries itself (Fish blocks ~10s on this at
startup) and bun-pty buffers output emitted before a data subscriber
attaches. Also fixes a few WebSocket transport reconnect races surfaced
by session creation now overlapping renderer setup.
2026-08-03 12:29:32 +03:00
Cursor AgentandSerhii Dziupin 20fc675af0 fix(skills): drive UI rename gating from server renamable flag
Expose authoritative renamable on skill list responses using the same
managed-root policy as renameSkill, drop the divergent UI path heuristic,
and remove an unused rejection-test fixture.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-03 09:25:25 +00:00
Serhii Dziupin 42eb18f82a Merge pull request #2576 from openchamber/feat/repository-local-skills-discovery-41dc
fix: discover repository-local .agents skills (#1159)
2026-08-03 12:20:42 +03:00
Cursor AgentandSerhii Dziupin 5defd1af75 fix(terminal): drop native ARGV0 for bun-pty via env -u
bun-pty merges the OS environ into PTY children, so deleting ARGV0 from the
JS env object alone left the AppImage path in the shell. Wrap Linux PTY
spawns with env -u ARGV0, clear native ARGV0 under Bun via libc unsetenv,
and always clear process.env even when no login-shell snapshot exists.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-03 09:12:35 +00:00
Cursor AgentandSerhii Dziupin ebf1b027cc fix(i18n): add German strings for custom LLM providers
Merge of main brought the de locale without the custom-provider keys
added on this branch, which broke the UI build type cast to I18nDictionary.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-03 08:56:23 +00:00
Cursor AgentandSerhii Dziupin be38fb8cf4 fix(desktop): strip AppImage ARGV0 before child shells (#2588)
AppImage exports ARGV0 into the process environment. zsh treats that as
argv[0] for every external command, which broke Python venv detection in
the integrated terminal and managed OpenCode sessions.

Clear ARGV0 in Electron before login-shell probing, refuse to re-apply it
from shell snapshots, and strip it from terminal PTY and managed OpenCode
launch environments.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-03 08:54:39 +00:00
Cursor AgentandSerhii Dziupin bfea13ef1d fix(skills): harden rename to managed roots and cover failures
Restrict in-place skill rename to managed skill directories, require
frontmatter name to match before moving, roll back/reject with tests,
hide rename in the UI for unmanaged paths, and drop unused toast keys.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-03 08:54:29 +00:00
Cursor AgentandSerhii Dziupin 094fb4fc40 merge main to pick up German locale for custom provider keys
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-03 08:53:06 +00:00