Commit Graph
2518 Commits
Author SHA1 Message Date
Serhii Dziupin 1765322ab6 Merge pull request #2622 from openchamber/feat/model-override-persistence-5865
fix(ui): persist manual model override across delegated subtask completion (#2404)
2026-08-04 17:27:03 +03:00
Cursor AgentandSerhii Dziupin 460675f57e Merge origin/main into deferred OpenCode restart branch.
Adopt main's providerAuth helpers (OAuth index preservation, OAuth-only API
key hiding, always-load auth methods) while keeping deferred Apply & Restart
for provider mutations.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-04 13:29:46 +00:00
Cursor AgentandSerhii Dziupin 65a1eec782 fix(ui): keep manual model override after delegated subtask completes
Synthetic subagent-completion nudges were treated as the latest user model
choice and rehydrated the agent default, while setAgent preferred the agent
pin over the session override. Skip synthetic prompts for restore, preserve
manual selection-store overrides, and prefer session agent models in setAgent.

Closes openchamber/openchamber#2404

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-04 12:46:48 +00:00
Serhii Dziupin fcd4eead56 Merge pull request #2621 from openchamber/feat/oauth-only-provider-auth-a542
fix(providers): hide API key form for OAuth-only providers
2026-08-04 15:45:52 +03:00
Cursor AgentandSerhii Dziupin bc24b8a836 fix(walkthrough): drop info tint from disabled Generate button
The status-info classes were winning over muted disabled styles, so the
button still looked actionable when no model was selected.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-04 12:43:31 +00:00
Cursor AgentandSerhii Dziupin 8d8bc8edcb fix(providers): hide models until credentials exist
For OAuth-only providers like Cursor, open the auth panel when
credentials are missing and omit the models list until auth/env
credentials are present so placeholder catalog entries are not shown
before login.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-04 12:39:16 +00:00
Cursor AgentandSerhii Dziupin 5c6eee331e fix(walkthrough): keep unauthenticated models out of the picker
Treat an empty allowedProviderIds list as allow-none, disable Generate
when no usable model is selected, and mute the button styling so it
reads as unavailable rather than actionable.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-04 12:35:25 +00:00
Cursor AgentandSerhii Dziupin 0bdf5ee4f3 fix(providers): hide API key form for OAuth-only providers
Only show the API key credential UI when a provider declares API auth
(or auth methods are still unknown). OAuth-only plugin providers such as
Cursor now show Connect/OAuth only, load auth methods on reconnect, and
skip an empty models section until models are discovered.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-04 12:30:47 +00:00
Cursor AgentandSerhii Dziupin 35f17e9e96 fix(walkthrough): hide unauthenticated models and disable Generate
Do not present a provider without a login as the selected walkthrough
model, and grey out Generate when readiness is false instead of showing
a login-error blocker or raw auth banner.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-04 12:22:54 +00:00
Cursor AgentandSerhii Dziupin abb396e080 fix(walkthrough): block unauthenticated providers with a friendly refusal
When the walkthrough small model resolves to a provider with no usable
login, readiness was still ready and generate returned a raw 500 message.
Refuse up front with no-provider-login and surface a blocker instead.

Closes openchamber/openchamber#2607

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-04 11:29:58 +00:00
Serhii Dziupin f47110c66f Merge pull request #2616 from makeittech/feat/restore-archived-sessions-2346
feat: add restore/unarchive for archived sessions
2026-08-04 13:41:00 +03:00
Serhii Dziupin 932d224916 feat: add german translations for session restore 2026-08-04 13:29:03 +03:00
Serhii Dziupin 577aca0437 feat: add restore/unarchive for archived sessions
Archived sessions had no way back to the active list: the only available
action was "Delete permanently". Add restore per session (sidebar context
menu, Archive page row) and in bulk (sidebar selection bar).

The OpenCode server cannot clear time.archived over HTTP — session.update
only applies the field for a finite number, so an omitted key is a no-op
and null is silently ignored (verified against opencode 1.18.12). Restore
therefore writes time.archived = 0: every client-side reader classifies
archive state by truthiness, so 0 reads as active in the UI, the event
reducer, and the OpenCode app/TUI.

The server's time_archived IS NULL list filter still excludes such rows,
so the global session cache no longer issues an archived:false request
for its active list. Full and per-directory loads now fetch once with
the inclusive flag and split client-side via splitGlobalSessionsByArchived,
which also halves per-directory refresh requests. Directory bootstrap
keeps the server filter because live child stores must not hold archived
sessions; a restored session re-enters its live store through the
authoritative session.updated event.

unarchiveSession/unarchiveSessions follow the archiveSession contract:
wait for server confirmation before reconciling stores, runtime-guard
every reconciliation, preserve partial batch results, and fail loudly
when the server keeps the session archived instead of toasting a
successful no-op.

Closes #2346
2026-08-04 13:21:04 +03:00
Serhii Dziupin 397b9840b7 Merge pull request #2595 from openchamber/feat/ctrl-l-add-selection-to-chat-58c2
feat(ui): Ctrl/Cmd+L adds selected text to chat
2026-08-04 11:33:51 +03:00
Cursor AgentandSerhii Dziupin f4f23704b8 fix(ui): drop Ctrl+L hint from text selection menu
Keep the shortcut in Help/Settings only; the floating selection chip
should stay quiet so the Add to Chat action is easier to scan.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-04 08:21:14 +00:00
Bohdan Triapitsyn 9d3d5c0852 fix(sessions): stop reporting a dispatched prompt that never landed
`prompt_async` answers 204 as soon as OpenCode forks the run and reports
every later failure only on the session event stream, so an unusable
model, agent, or variant produced a session with no message while the
result still claimed `promptDispatched: true`.

Validate an explicitly requested model, agent, and variant against the
directory's own agent and provider lists before any session, worktree, or
goal is created, and confirm a new user message actually reached the
session before reporting the dispatch. A failed or empty lookup never
turns a valid selection into a rejection.
2026-08-04 11:08:28 +03:00
Serhii Dziupin f710b49854 Merge pull request #2600 from kydorn/fix/linux-terminal-launcher-misattribution
fix(electron): require TerminalEmulator category for terminal appId on Linux
2026-08-04 10:22:25 +03:00
Bohdan Triapitsyn f28d36a23f release v1.18.0 2026-08-04 02:29:53 +03:00
Bohdan Triapitsyn 540d2ae46a fix(sync): render sessions in worktrees created while the client is running #2603 2026-08-04 02:05:29 +03:00
Bohdan Triapitsyn e9b957ffd2 feat: add info button variant and reuse it in mobile sessions sheet
Adds a new info-styled button variant for consistent secondary actions.
Updates mobile sessions sheet actions to use the shared Button component.
Cleans up the mobile footer buttons with the shared sizing and styling system.
2026-08-04 01:54:55 +03:00
Bohdan Triapitsyn 3aeca4893e docs(sync): correct the ownership precedence the fix inverted
Review found the owning documentation still describing the behaviour this
branch replaced, in one case stacked directly above the new docstring saying
the opposite. Holding a session proves containment, not ownership, so every
text that called store membership the authoritative mapping was actively
misleading for the module whose wrong answer misroutes every send.

Corrected in the module docstring, the resolution module's precedence
description, the sync-refs helper it points at, and the sync DOCUMENTATION.md
table and rules.

The debug report built its authoritative value membership-first, so for exactly
the scenario this branch fixes it reported the parent directory and could raise
a source-disagreement alert while routing was in fact correct. It now uses the
same record-first order as the resolver.

The CLI timeout comment claimed the wait and provisioning windows were additive
while the code took the larger of the two. The server provisions the worktree
inside session creation, before it waits for the session to go idle, so they do
run in sequence: the windows are now summed and the tests pin both cases.
2026-08-04 01:50:14 +03:00
Bohdan Triapitsyn baeefe1ae5 fix(i18n): add German download label 2026-08-04 01:37:48 +03:00
Bohdan Triapitsyn a7b61506dc style: adjust openchamber dark theme colors
Softens the dark surface and syntax background tones
Updates foreground, selection, and cursor contrast
Keeps markdown, chat, and tool text aligned with the new palette
2026-08-04 01:37:03 +03:00
Bohdan Triapitsyn 1684e6f660 fix(files): hide desktop-only reveal action in browser clients #2597
fix(files): hide desktop-only reveal action in browser clients
2026-08-04 01:35:57 +03:00
Bohdan Triapitsyn b6c58df949 fix(cli): give worktree provisioning a timeout that fits the work
Creating a session with a worktree reported "Request to /api/openchamber/control
timed out after 4000ms" while the worktree was in fact created, leaving the user
with a failure message, a real worktree, and no session id. Reported alongside
worktree creation appearing to take forever.

The client HTTP timeout was extended only when the caller asked to wait for the
session. Provisioning a worktree is slow on its own: it runs git against the
repository and prepares a new directory. Measured on a cold path immediately
after a restart it takes about four seconds, which lands exactly on the four
second default and explains why this failed intermittently rather than always.
A warm run finishes in well under two.

The timeout now follows the work being requested rather than only the wait
flag, and covers whichever of the two windows is longer. The server always
completed the operation, so nothing about the outcome changes: only the client
stops abandoning it.

Verified by creating a worktree on the cold path immediately after a restart,
which previously failed here: 4004 ms and 1376 ms, both reported ok.
2026-08-04 01:33:56 +03:00
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 0939b21d3c feat: self-heal Electron installs before dev and postinstall
Adds an Electron install check that repairs incomplete or wrong-architecture binaries
Runs the check during root postinstall and before electron dev startup
Adds tests and docs for the new ensure:electron workflow
2026-08-04 00:58:10 +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
Pablo a40654dfbb fix(electron): require TerminalEmulator category for terminal appId on Linux
desktopEntryMatchesApp used a loose substring match that accepted any
desktop entry whose Exec line mentioned a terminal launcher. A non-
terminal app launched via xdg-terminal-exec (e.g. a TUI helper) was
mis-attributed to the generic 'terminal' appId, so 'Open in Terminal'
launched the helper script and the picker showed the helper's icon.

For appId === 'terminal' only, require Categories=TerminalEmulator;
ghostty/iterm2 keep name-based matching. The xdg-terminal-exec /
gnome-terminal / konsole / xfce4-terminal / x-terminal-emulator fallback
chain is unchanged, so non-conformant custom terminal entries still
launch. Adds a shared isTerminalEmulatorEntry predicate used by both
buildLinuxOpenSpecs (launch) and buildLinuxInstalledApps (icon).

Smoke test gains a generic helper-app fixture (non-terminal app whose
Exec uses xdg-terminal-exec) plus a TerminalEmulator entry, with
assertions that the helper script is never launched and the terminal
emulator's icon resolves instead of the helper's. Two byte-distinct
PNGs keep the icon assertion a real discriminator.
2026-08-03 18:08:24 +02: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
Cursor AgentandSerhii Dziupin 682c42df8f fix(ui): harden Ctrl+L selection capture and menu delivery
Cover CodeMirror and DOM capture paths in tests, collapse text-control
selections after capture, and deliver the desktop Edit-menu action over
a single IPC channel so append cannot double-fire.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-03 14:24:22 +00: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
Serhii DziupinandClaude Sonnet 5 f02969548a fix(files): hide desktop-only reveal action and label download in browser clients
Reveal-in-file-manager was always offered whenever the server exposed
revealPath, including in a plain browser tab where there is no local
file manager to reveal into. Gate it behind a new isBrowserClientRuntime
check (web platform, no Electron shell) and relabel the save action to
"Download" for that case, since it triggers a browser-style file
download rather than an in-place save.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 17:11:41 +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
Cursor AgentandSerhii Dziupin ead9aad33b Merge origin/main into deferred OpenCode restart branch.
Resolve ProvidersPage and lifecycle conflicts with custom providers and
AppImage ARGV0 stripping. Address review follow-ups: OAuth index helper +
tests, single auth-methods load trigger, shared Google env-alias module with
VS Code parity coverage, and deferred restart for custom provider upsert.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-03 13:57:05 +00: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