Commit Graph
292 Commits
Author SHA1 Message Date
Bohdan Triapitsyn 08b866136e fix(server): normalize encoded directory headers 2026-06-24 00:43:11 +03:00
Bohdan Triapitsyn 2fd86db6a7 fix(auth): trim opencode server username 2026-06-23 21:52:51 +03:00
bashrusakhandLeonid Skorobogatyy 4feddfa810 fix(auth): honor OPENCODE_SERVER_USERNAME env var for basic auth (#1705)
Fix #1685: the Basic auth header for the OpenCode server was hardcoded
to use the username 'opencode', ignoring OPENCODE_SERVER_USERNAME. Users
who set a custom username got 401 errors because the server expected a
different credential.

Both call sites (web server auth-state-runtime.js and VS Code
extension opencode.ts) now read process.env.OPENCODE_SERVER_USERNAME
with a fallback to 'opencode' to preserve prior behavior.

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 21:50:14 +03:00
Szasz AttilaandBohdan Triapitsyn eae09d4576 fix(settings): persist per-model visibility and sibling selector state (#1700)
* fix(settings): persist per-model visibility and sibling selector state

The server-side settings sanitizer only allowlisted favoriteModels and
recentModels, so hiddenModels, collapsedModelProviders, recentAgents, and
recentEfforts were stripped on every write to settings.json — per-model
visibility and collapsed-provider state silently reset on every container
redeploy or settings reload.

Add the four missing fields to sanitizeSettingsUpdate:
- hiddenModels: sanitizeModelRefs(..., 1024) — same shape as favoriteModels;
  1024 covers dense multi-provider setups while bounding persistence/memory.
- collapsedModelProviders: normalizeStringArray with Array.isArray gate
  (matches usageDropdownProviders).
- recentAgents: normalizeStringArray (Array<string> per ui-store).
- recentEfforts: new sanitizeRecentEfforts validating Record<string, string[]>
  (shape confirmed in ui-store + addRecentEffort action); trims/dedupes keys
  and variants, caps at 128 keys x 5 variants/key (5 matches client slice).

No ui-store version bump or migration: zustand's default merge spreads
persisted state over defaults, so missing fields fall back to [] / {} until
the next toggle. favoriteModels and recentModels are untouched.

Tests: 8 new cases in settings-helpers.test.js using the real
sanitizeModelRefs / normalizeStringArray — round-trips, empty-[] parity with
favoriteModels, garbage rejection, and a full-payload regression test.

* fix: sync model selector settings

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-23 21:46:23 +03:00
FanFan4204andBohdan Triapitsyn efd621b087 fix: handle non-ISO-8859-1 characters in fetch headers and Content-Disposition (#1673)
* fix: handle non-ISO-8859-1 characters in fetch headers and Content-Disposition

Browser Headers API rejects characters above U+00FF. The x-opencode-directory header carries raw filesystem paths, which breaks when paths contain Chinese/CJK characters. Also fixes Content-Disposition for non-ASCII filenames per RFC 5987.

* refactor: export header sanitization helpers, deduplicate, add tests

Export isLatin1Safe and sanitizeHeadersForBrowser from runtime-fetch.ts so VS Code webview can import them instead of duplicating the logic. Add tests: isLatin1Safe boundary checks, sanitizeHeadersForBrowser encoding/deduplication, runtimeFetch round-trip encode/decode, and Content-Disposition RFC 5987 output for both ASCII and non-ASCII filenames.

* fix: mark encoded directory headers

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-23 19:49:44 +03:00
Baruch Vitorino eab8862268 fix(quota): handle MiniMax M3/Token Plan API changes (#1589)
Extract shared MiniMax provider logic into minimax-shared.js factory
module used by both minimax-coding-plan and minimax-cn-coding-plan
as thin wrappers.

Endpoint fallback:
- Try /v1/token_plan/remains (M3/Token Plan) first
- Fall back to legacy /v1/api/openplatform/coding_plan/remains
- fetchEndpoint wrapped in try/catch so network/parse errors
  return null instead of throwing, ensuring fallback always runs

Model selection (pickChatModel):
- Prefer MiniMax-M* entries with non-zero total_count (Token Plan M3)
- Fall back to general/chat/text model names (legacy Coding Plan)
- Fall back to any entry with current_interval_remaining_percent
- Ultimate fallback to model_remains[0]

Usage calculation:
- token_plan endpoint: usage_count = remaining, so used = total - remaining
- coding_plan endpoint: usage_count = consumed (legacy behavior)
- Prefer current_interval_remaining_percent when count fields are zero
  (legacy Coding Plan accounts with percentage-based quotas)
- remains_time used as fallback for window duration (in milliseconds,
  confirmed via live API: 9664502ms = 2.68h in 5h window)

Window status:
- Respect current_weekly_status field: status 3 means the window is
  not applicable for the current plan tier (e.g. legacy plans without
  weekly limits). These windows are omitted from the result.
- Default to active when status field is absent (backward compatible).

Fixes #759 (percentage showing empty/null for legacy Coding Plan
accounts and incorrect percentages for M3/Token Plan accounts).
2026-06-23 11:31:17 +03:00
Bohdan Triapitsyn 4d63278efd feat: add SSH commit signing to git identities
Configure commit signing per Git identity
Apply SSH signing settings automatically
Support signing in web and VS Code
2026-06-18 23:51:40 +03:00
Bohdan Triapitsyn 91de51d1a5 fix: deduplicate desktop notifications and tighten notification text extraction
Desktop notifications no longer duplicate when native delivery succeeds
Reasoning chain-of-thought is excluded from notification body text
Untyped message parts are ignored in notification text extraction
2026-06-16 15:21:27 +03:00
Bohdan Triapitsyn 8f1da2f728 fix: stabilize session diagnostics and Windows session loading
Fix duplicated health probe URL in diagnostics
Share session list proxy handling across platforms
Avoid repeated hanging session requests on Windows
2026-06-16 14:05:44 +03:00
Bohdan Triapitsyn e982bd9388 fix: prevent agent deletion from disabling built-ins
Stop delete from creating disable overrides
Delete only the selected agent scope
Keep web and VS Code behavior aligned
2026-06-16 13:38:24 +03:00
Bohdan Triapitsyn a45376d585 perf: migrate chat rendering to virtua (#1651)
* refactor: migrate chat history virtualization to virtua

* refactor: render loaded chat history directly

* refactor: finish virtua migration

* perf: defer tool body rendering

* perf: queue deferred tool body mounts

* perf: quiet and defer markdown file probes

* perf: defer markdown code highlighting

* perf: stabilize markdown plugin lists

* perf: defer mermaid markdown rendering

* perf: delay markdown file reference annotation

* perf: attach markdown table listeners on demand

* perf: trim markdown render overhead
2026-06-15 03:29:40 +03:00
Bohdan Triapitsyn e372c8d8cb perf: instant startup via cache hydration + decoupled readiness (#1650)
* perf(startup): hydrate providers/agents from cache (stale-while-revalidate)

Persist last-known provider/agent snapshots instead of stripping them, so the
model/agent pickers paint instantly on cold start. Freshness is preserved by the
background refresh in initializeApp() and activateDirectory() (which overwrite on
success) and by the existing provider/agent config-change subscriptions, so the
prior stale-provider regression stays fixed without blanking the UI during fetch.

* perf(startup): cache directory session list for instant sidebar

Persist a capped slice of each directory's session list and seed the child store
from it on creation, so the sidebar paints chats immediately on cold start.
Bootstrap phase-3 loadSessions overwrites with the fresh list; its empty-list
race guard preserves the seeded sessions during OpenCode warmup.

* perf(startup): hold API requests through OpenCode warmup instead of 503

The readiness gate returned 503 the instant OpenCode wasn't ready, pushing the
client into an exponential-backoff retry loop (500ms -> 1s -> ...) that wasted
seconds of cold-start time and could fail bootstrap outright. Now hold the
request and poll readiness up to a bounded window so the first call succeeds as
soon as OpenCode is up (typically sub-second); still 503 fast past the window so
a genuinely-down server doesn't hang. Adds coverage for both paths.

* perf(startup): surface cached providers/agents in pickers (optimistic readiness)

The model/agent pickers gated purely on isInitialized, so they showed
"Loading…" for the entire init round-trip even when provider/agent data was
already hydrated from cache — making the persisted-cache work invisible. Treat
the pickers as ready as soon as cached providers are present (stale-while-
revalidate), so they paint last-known models/agents instantly and refresh in the
background. First-ever launch (no cache) still shows Loading until init.

* perf(startup): don't abort directory bootstrap on transient phase-1 failure

A failed initial path.get OR session.status aborted the whole directory
bootstrap, stranding it in loading and skipping phase 2/3 (session load).
session.status is live data the event pipeline keeps current, and path.get is
tolerable once a project is resolved from global state. Now only a total
failure (or path.get failing with no resolved project) aborts, so the sidebar
and chat keep advancing and loading sessions through warmup hiccups.

* perf(startup): don't bootstrap directories from archived sidebar rows

Each sidebar session row called useDirectoryStore(dir), which defaulted to
bootstrap:true and triggered a full directory bootstrap. Archived sessions point
at dozens of (often deleted) worktrees, so on startup this fired a session-list
fetch + 6x2s empty-retry storm per dead directory (the logs the user saw). The
store ref there is only read on-demand via getState() in export handlers, never
subscribed, so archived rows don't need it bootstrapped. Add a { bootstrap }
option to useDirectoryStore and skip bootstrap for archived rows; active rows
still bootstrap so live cross-directory session/status keeps aggregating.

* perf(startup): stop empty-session bootstrap retry storm on web/desktop

The post-bootstrap retry re-ran the full directory bootstrap 6x2s whenever the
session list came back empty, on the theory that empty meant OpenCode wasn't
ready. But loadSessions already retries transient failures twice over
(listGlobalSessionPages throws on 5xx and retries internally), so on web/desktop
an empty result is authoritative — the directory genuinely has no sessions (e.g.
deleted worktrees referenced only by archived sessions). That produced the
dozens of '[bootstrap] sessions empty ... 6 attempts; giving up' log storms.
Gate the retry to VS Code, where the bridge can return an empty 200 during
warmup that the inner retries can't catch.

* perf(startup): scope provider/agent config to project (worktrees inherit)

Providers/agents/defaults are project-level, but were keyed per directory, so a
worktree fetched and cached its own snapshot — duplicating the parent project's
load (the trace showed initializeApp loading the worktree and activateDirectory
loading the project concurrently, ~8s of redundant background work).

- resolveConfigDirectory() maps a worktree to its owning project; loadProviders
  /loadAgents/activateDirectory now key by it, so a worktree reuses one shared
  project snapshot. activateDirectory resolves up-front so activeDirectoryKey and
  the snapshot key always match (picker stays consistent); the OpenCode working
  directory is unaffected.
- Add a 30s runtime freshness guard so the stale-while-revalidate background
  refresh skips re-fetching config that was just loaded (initializeApp then
  activateDirectory for the same project), and to avoid churn on rapid project
  switches. Config-change invalidation clears the snapshot, which bypasses the
  guard, so freshness never masks a needed refresh.

* fix(sidebar): default archived sessions to hidden to avoid startup flash

useSessionDisplayStore defaulted showArchivedSessions to true, so on startup
archived sessions rendered by default and then vanished once the persisted
preference rehydrated to hidden — a visible flash. Default to hidden so the
pre-hydration state is the quiet one; users who opted into showing archived keep
their persisted true (default change doesn't override persisted state).

* perf(startup): persist worktree->project mapping to kill cold double-load

The worktree->project map (availableWorktreesByProject) is populated by async git
discovery, so it isn't ready when initializeApp runs — a worktree's first config
load couldn't resolve to its project and duplicated the project's provider/agent
load, saturating OpenCode during cold start (the source of the slow first
createSession/send the user observed). Cache resolved worktree->project mappings
to localStorage so resolveConfigDirectory resolves synchronously at init on
subsequent launches; the project is loaded once and activateDirectory hits the
freshness guard. worktree->project is immutable so a cached entry is safe; live
resolution still populates/corrects the cache.

* perf(startup): persist worktree map for instant sidebar + first-launch keying

Worktree discovery is async (git), so availableWorktreesByProject was empty at
startup: the sidebar worktree list appeared late, and useConfigStore couldn't
resolve a worktree to its project on the first launch (causing the cold
worktree+project double-load). Persist the discovered worktree map to
localStorage and seed it synchronously on store init (stale-while-revalidate:
discovery refreshes in the background via the existing setState, which now
write-through persists). The sidebar paints worktrees instantly and
resolveConfigDirectory resolves the project from the very first launch.

* perf(startup): coalesce concurrent duplicate OpenCode reads in runtimeFetch

On cold start the sync bootstrap and the config store independently fire the same
idempotent reads (providers, config, path, agents, project) concurrently with no
shared dedup, saturating the single OpenCode process and delaying work queued
behind it (e.g. createSession). Coalesce genuinely-concurrent identical GETs to
those read endpoints at the transport layer so OpenCode does the work once; each
caller receives an independent response clone. Tightly scoped: GET only,
allowlisted read paths, never event streams, never a signal-bearing request (so
one caller's abort can't cancel the shared fetch). Entries clear on settle, so it
only shares overlapping in-flight requests — never a stale response.

* perf(startup): cache git branches so the draft branch selector paints instantly

The branch selector above the composer was the slowest-loading element: it's
gated behind a cold 'git branch' fetch (useGitStore, not persisted). Cache the
per-directory branch list to localStorage and seed the store on init (with
isGitRepo:true so the selector's gate passes), and write the cache on every
successful fetchBranches. The ChatInput draft-branch effect now refreshes on
staleness (>30s) rather than mere absence, so seeded branches show immediately
and still refresh in the background without a spinner — no stale-forever
regression. Only the branch list is cached; status/log/diff are untouched.
2026-06-15 03:16:34 +03:00
Bohdan Triapitsyn 1762c1a289 Polish diff file actions 2026-06-14 16:17:30 +03:00
Bohdan Triapitsyn f645d57c93 Stage, unstage, and discard individual diff hunks
Add per-hunk staging, unstaging, and discarding to the Changes diff
view, so a single change region inside a file can be acted on in
isolation instead of forcing whole-file stage/revert. The change is
wired end-to-end across the web server, the shared UI runtime API
contract, and the VS Code extension, with Electron inheriting the web
path unchanged (it boots the server in-process).

Server
------
- New `applyHunk(directory, filePath, { patch, action })` in
  packages/web/server/lib/git/service.js. It resolves the repository
  context and validates the file path with the same helpers used by
  stageFiles/unstageFiles (resolveGitFileContext +
  validateRepositoryFilePaths), then writes the single-hunk patch to a
  temporary file in the OS temp dir (never inside the repo, so it
  cannot show up as an untracked file) and runs `git apply` with flags
  chosen per action:
    stage   -> git apply --cached          (working tree -> index)
    unstage -> git apply --cached --reverse (index -> working tree)
    discard -> git apply --reverse          (revert in working tree)
  A `git apply --check` runs first with the same flags, so a stale
  hunk that no longer applies fails with a clear "Hunk no longer
  applies - refresh and try again" message instead of leaving a
  partial mutation. The patch's target path is parsed and must match
  the requested file (with /dev/null tolerated for new/deleted files),
  preventing a patch from silently targeting a different path. The
  whole operation runs inside withGitIndexMutationQueue to avoid
  racing with concurrent stage/unstage. The temp file is removed in a
  finally block.
- New `POST /api/git/apply-hunk` route in routes.js, registered
  alongside stage/unstage. Validates directory, path, non-empty patch,
  and action before delegating.
- DOCUMENTATION.md updated with the new service entry.

Patch extraction
----------------
- packages/ui/src/lib/diff/patchFileDiff.ts gains
  splitPatchIntoHunks(patch) and extractHunkPatch(patch, hunkIndex).
  They keep the original file header (diff --git / index / --- / +++)
  and emit exactly one @@ hunk per standalone patch, which is what
  `git apply` expects. Each emitted patch is guaranteed to end with a
  trailing newline (without it git apply reports "corrupt patch").

Runtime API contract
--------------------
- GitAPI (packages/ui/src/lib/api/types.ts) gains optional
  stageGitHunk / unstageGitHunk / revertGitHunk, matching the
  stageGitFiles? / unstageGitFiles? precedent so runtimes that do not
  support it degrade gracefully.
- gitApi.ts delegates to the registered runtime git API, falling back
  to gitApiHttp, exactly like the existing whole-file helpers.
- gitApiHttp.ts posts to /api/git/apply-hunk.
- Web runtime composes the three methods in packages/web/src/api/git.ts.

VS Code parity
--------------
- packages/vscode/src/gitService.ts adds applyGitHunk(), implemented
  natively with the existing execGit helper + a temp patch file +
  `git apply` (--cached / --cached --reverse / --reverse), mirroring
  the server's --check-first safety and temp-file cleanup.
- bridge-git-runtime.ts handles the new api:git/apply-hunk bridge
  message; webview/api/git.ts sends it. VS Code users get identical
  stage/unstage/discard-hunk behavior.

UI
--
- New DiffHunkActions component renders a compact per-hunk strip
  above each expanded file diff in the Changes view. Each hunk chip
  shows its +additions / -deletions counts and offers:
    working scope -> Stage + Discard
    staged scope  -> Unstage
  Clicking extracts that hunk's standalone patch via
  extractHunkPatch(patch, hunkIndex) and calls the runtime git API.
  Because the chip index comes directly from fileDiff.hunks[] and the
  patch is sliced in the same order, the hunk the user sees is always
  the hunk that gets applied. While any action is in flight all buttons
  disable to prevent conflicting concurrent mutations; the per-hunk
  spinner reflects in-flight state.
- DiffView wires DiffHunkActions into InlineDiffViewer (text diffs
  only; binary/image and full-file-content modes are excluded since
  they have no patch). MultiFileDiffEntry passes directory/staged
  through and handles onHunkApplied by bumping the diff reload nonce
  (so the file's diff re-fetches and the affected hunk disappears)
  and refreshing git status (so file counts and the staged/changed
  scope update). Hunk actions are therefore available wherever the
  default patch-context diff is shown.

i18n
----
- 10 new keys (diffView.hunk.*) added to all 9 locales (en, es, fr,
  ko, pl, pt-BR, uk, zh-CN, zh-TW), including stage/unstage/discard
  labels, tooltips with the hunk index, a stale-hunk error message,
  and an unsupported-runtime fallback.

Tests
-----
- packages/ui/src/lib/diff/patchFileDiff.test.ts covers
  splitHunks/extractHunkPatch: multi-hunk split, header preservation,
  single-hunk and empty patches, out-of-range indices.
- service.test.js adds an applyHunk suite that builds real temp repos
  with two separate hunks and verifies: staging one hunk leaves the
  other unstaged, discarding reverts only the targeted hunk in the
  working tree, unstaging removes only one hunk from the index, and a
  retargeted patch (different file path) is rejected. Also covers
  invalid-action / missing-hunk-header validation.
- packages/web/src/api/git.test.ts mock completed with the new methods
  (and previously-missing exports that prevented the test from
  loading) and asserts the three hunk methods are exposed.
- routes.test.js continues to pass under bun.

CHANGELOG updated under [Unreleased].
2026-06-14 10:58:23 +03:00
Bohdan Triapitsyn 2538a08370 Fix git worktree root normalization 2026-06-14 01:07:11 +03:00
Bohdan Triapitsyn cf8eac966e Refine stacked diff view 2026-06-13 22:30:03 +03:00
Bohdan Triapitsyn ca87428216 fix: harden file previews and downloads 2026-06-13 01:44:03 +03:00
Roberto BertóandBohdan Triapitsyn 15813b61c3 feat: add opt-in docked editor toolbar (#1562)
* feat: add opt-in docked editor toolbar

* fix: align docked editor toolbar actions

* fix: move docked toolbar setting to navigation

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-13 00:01:07 +03:00
Bohdan Triapitsyn 1a623f9b1c Add inline PDF file preview 2026-06-12 20:08:19 +03:00
Bohdan Triapitsyn 782bc92b15 Forget unmanaged orphan worktrees safely 2026-06-12 18:36:06 +03:00
Bohdan Triapitsyn ea3bb103eb Restrict orphan worktree cleanup 2026-06-12 18:33:22 +03:00
Bohdan Triapitsyn 106b31a407 Harden remote API security boundaries 2026-06-12 18:24:07 +03:00
Bohdan Triapitsyn c703db2745 fix: stop forwarding client auth to OpenCode and harden home/session state
Packaged desktop showed no sessions in 1.12.4. Root cause: the sanitized
session-list proxy path added in #1538 forwarded the renderer's
"authorization" header (the OpenChamber UI client token) to the managed
OpenCode upstream alongside the managed "Authorization" credential.
OpenCode does not recognize UI client tokens, so every session-list
request answered 401 — only in the packaged app, because only its
renderer (openchamber-ui:// origin) attaches a bearer token; dev web and
dev Electron run same-origin without one. The legacy http-proxy path
overwrote the header correctly, which is why everything except session
lists kept working.

Proxy fix:
- proxy-headers: filter the client "authorization" header out of
  forwarded request headers; the OpenCode upstream must only ever see
  its own managed credentials. Covered by tests.

Desktop cwd:
- electron: launch the managed OpenCode CLI from the user home instead
  of app userData, matching upstream desktop behavior. userData-as-cwd
  made OpenCode treat the app-data folder as a separate empty workspace.

Home directory poisoning loop:
- directoryPersistence: stop replaying localStorage homeDirectory
  through synchronizeHomeDirectory on boot/auth resync. The persisted
  value is only a boot-time cache; replaying it re-wrote stale values
  (e.g. a project path) into desktop settings on every start, overriding
  the authoritative /api/fs/home resolution.
- persistence: never overwrite an injected window.__OPENCHAMBER_HOME__
  with a persisted value.
- useDirectoryStore: host switches happen in place (no reload), so
  re-resolve home from the new runtime's /api/fs/home on endpoint
  change instead of keeping the previous host's value.
- opencode client: only short-circuit to the injected desktop home when
  the active runtime is local; remote runtimes ask /api/fs/home.

Settings hygiene:
- persistSettings: log field names only — change payloads can carry
  credentials (UI password, client tokens, tunnel tokens) that must not
  reach the log file; drop step-by-step log chatter.
- validateProjectEntries: only stat project paths when the incoming
  update actually touches the projects list, not on every settings save.
- remove the write-only approvedDirectories setting everywhere and add
  a migration that strips the stale key from persisted settings.

Tests:
- usePluginsStore.test: register an own runtime-fetch module mock so the
  suite is independent of process-global mock.module leakage from other
  files, and restore globalThis.fetch after the suite.
- persistence.test: clean up the window global created for the suite.
2026-06-12 01:53:38 +03:00
Hristo KaramanlievandBohdan Triapitsyn 9685630436 fix: lighten session list payloads (#1538)
* lighten session list

* fetch full session on open

* sanitize session list

* sanitize global sessions

* preserve revert markers in session lists

* fix: preserve session metadata in list sanitizers

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-11 23:06:19 +03:00
284d72bef2 Keep notification SSE stream alive behind proxies (#1516)
* fix: keep notification SSE stream alive

* Fix PR comments

* fix: cover notification stream error cleanup

---------

Co-authored-by: Konstantin Zolin <zolin_ka@vk.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-11 19:48:06 +03:00
Bohdan Triapitsyn f26950fa4e fix: treat gh CLI token as GitHub account 2026-06-11 19:30:19 +03:00
Bohdan Triapitsyn 465732858f fix: clarify GitHub token settings 2026-06-11 18:59:58 +03:00
Tom Rochette 33e614c76b Fallback to gh CLI credentials if available (#1515)
Adds `gh` CLI as a GitHub credential fallback for users who already have
`gh auth login` configured locally. OpenChamber-owned OAuth credentials
remain the primary source of truth; the `gh` token is only used when no
stored OpenChamber GitHub access token exists and the fallback is not
disabled.

The fallback is implemented as a credential provider only: GitHub features
continue to use the existing Octokit/GitHub API paths for issues, pull
requests, checks, merges, and related operations. The PR does not replace
those endpoints with `gh issue` or `gh pr` CLI commands.

Server changes:
- Add `gh-cli-credential.js` to read `gh auth token` with a bounded timeout.
- Cache the `gh` token lookup for 30 seconds, including negative results,
  to avoid repeated subprocess spawning on status/polling paths.
- Hide the subprocess window on Windows via `windowsHide: true`.
- Clear the gh CLI token cache when the fallback setting changes.
- Update `getOctokitOrNull()` to prefer stored OpenChamber OAuth tokens and
  fall back to the `gh` token only when enabled.
- Add `ghCliDisabled` persistence in the existing settings file with atomic
  writes and `0o600` file permissions.
- Add `POST /api/github/auth/gh-cli` to enable or disable the fallback.
- Extend `/api/github/auth/status` with `ghCli` metadata: availability,
  disabled state, active state, and active user when applicable.

UI/runtime changes:
- Extend `GitHubAuthStatus` and `GitHubAPI` with gh CLI fallback metadata
  and toggle support.
- Add web RuntimeAPI support for toggling the gh CLI fallback through
  `runtimeFetch`, preserving active runtime/remote target behavior.
- Add deterministic VS Code unsupported handling for the gh CLI toggle.
- Update GitHub Settings to show gh CLI availability and active status.
- When gh CLI is the active auth source, show it in the connected account
  card and offer Disable instead of Disconnect.
- Keep Add Account available so users can still connect an OpenChamber OAuth
  account, which then takes priority over gh CLI.
- Add localized gh CLI settings strings across supported settings locales.

Fixes addressed during review:
- Removed unreachable UI branches in the inactive gh CLI card.
- Avoided duplicate and repeated `gh auth token` subprocess calls.
- Hardened settings file permissions for the new persisted flag.
- Routed the gh CLI toggle through the RuntimeAPI/runtimeFetch path instead
  of direct browser `fetch`.
- Added targeted tests for hidden subprocess options and negative-result
  cache behavior.
- Fixed a VS Code webview Response body typing issue that blocked type-check.
2026-06-11 18:43:41 +03:00
Bohdan Triapitsyn 3e7d3f85c7 fix: show Cursor plan limit progress
Calculates plan limit usage from remaining balance
Restores the Cursor plan limit progress bar
2026-06-11 02:01:53 +03:00
Bohdan Triapitsyn a6571aa8b7 fix: avoid unnecessary macOS folder prompts on desktop startup
Start managed OpenCode from the app data directory instead of the home folder
Prevent unnecessary Desktop, Documents, Downloads, and Music access prompts
Add coverage for configured OpenCode working directory
2026-06-11 01:06:25 +03:00
Bohdan Triapitsyn b46153801b feat: add Cursor usage quota tracking
Adds Cursor as a supported quota provider
Reads Cursor auth from env, token files, or local app data
Improves Cursor usage labels in the UI
2026-06-11 00:16:48 +03:00
85bf7c7563 fix(opencode): accept non-2xx status codes in probe-url to support redirects (#1471)
Co-authored-by: mdbetancourt <mdbetancourt@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-10 18:54:41 +03:00
Bohdan Triapitsyn eff6f46ad9 feat: improve mobile UX (#1591)
Added a mobile MCP overlay so MCP tools can be opened and managed from the mobile UI without relying on desktop-only dropdown behavior.
Improved mobile session panel touch handling so tapping the status/session area opens the right panel reliably on phones and tablets.
Cleaned up mobile usage provider metadata by removing duplicate rows, hiding unset providers, and showing provider logos consistently.
Added eager loading for provider logos used in mobile usage views to avoid delayed or missing icons when the panel opens.
Refined the mobile update and about flows in OpenChamber settings so release/update information is easier to read on small screens.
Adjusted related layout, header, VS Code layout, command palette, and settings text/localization details needed for the mobile polish.
2026-06-10 12:00:10 +03:00
db17f2d95a fix(preview): rewrite inline module scripts in proxy HTML responses (#1470)
Co-authored-by: mdbetancourt <mdbetancourt@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-09 19:04:50 +03:00
nerdosaurusandBohdan Triapitsyn e6338e5c71 fix: harden atomic file writes (#1453)
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-08 20:06:20 +03:00
yangyaofeiandBohdan Triapitsyn a6bf73b245 fix(tts): allow remote custom provider URLs on desktop runtime (#1439)
* feat(tts/stt): add API key support for OpenAI-compatible custom providers

## Problem
Custom (OpenAI-compatible) TTS/STT provider in Voice Settings has no way to
pass an API key or bearer token. Many self-hosted or third-party compatible
servers require authentication, making them unreachable from OpenChamber.

The server-side TTS route already accepts an `apiKey` parameter, but the
frontend never sends it. The STT route hardcodes `'not-required'`.

## Implementation
- Add `openaiCompatibleApiKey` to Zustand config store, persisted to localStorage
- Add API Key input field in VoiceSettings.tsx under the custom provider section
- Wire `openaiCompatibleApiKey` through useServerTTS to the TTS backend
- Add `apiKey` field to AudioStreamConfig for STT, forwarded as X-API-Key header
- Update server STT route to accept and forward X-API-Key to transcribeAudio
- Update stt.js to use client-provided apiKey before falling back to env var

## Files changed
- packages/ui/src/stores/useConfigStore.ts
- packages/ui/src/components/sections/openchamber/VoiceSettings.tsx
- packages/ui/src/hooks/useServerTTS.ts
- packages/ui/src/hooks/useBrowserVoice.ts
- packages/ui/src/lib/voice/audioStreamService.ts
- packages/web/server/lib/tts/routes.js
- packages/web/server/lib/tts/stt.js

* feat(tts/stt): add separate API key support for custom TTS and STT providers

## Problem
Custom (OpenAI-compatible) TTS and STT providers in Voice Settings have no way
to pass API keys. Many self-hosted or third-party compatible servers require
authentication, making them unreachable from OpenChamber Desktop (Electron).

## Implementation
- Add `openaiCompatibleApiKey` for TTS (persisted to localStorage, passed in JSON body)
- Add `sttApiKey` for STT (persisted to localStorage, passed via Authorization: Bearer header)
- Two independent keys: TTS and STT are configured separately
- STT authentication follows OpenAI standard (Authorization: Bearer <token>)
- TTS authentication follows existing pattern (apiKey in JSON body)
- Backend STT route extracts bearer token from Authorization header
- Backend STT service prefers client-provided key over OPENAI_API_KEY env var

## Fixes
- Fixed P1: ConfigStore interface now declares setOpenaiCompatibleApiKey setter
- STT API key is only forwarded when sttProvider === 'server' (not leaked to other providers)

## Files changed (7)
- packages/ui/src/stores/useConfigStore.ts
- packages/ui/src/components/sections/openchamber/VoiceSettings.tsx
- packages/ui/src/hooks/useServerTTS.ts
- packages/ui/src/hooks/useBrowserVoice.ts
- packages/ui/src/lib/voice/audioStreamService.ts
- packages/web/server/lib/tts/routes.js
- packages/web/server/lib/tts/stt.js

* fix: refresh server STT callback when API key changes

* fix(tts): allow remote custom provider URLs on desktop runtime

## Problem
Custom OpenAI-compatible TTS/STT provider URLs are restricted to
localhost addresses only. Remote URLs are silently rejected at the
server boundary, making custom cloud-based TTS/STT providers unusable
in the desktop app.

## Root Cause
`base-url.js` rejects non-localhost URLs unless
`OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS=true` is set, but this
env var is never set by desktop shells and is not exposed in settings.

## Solution
Use the existing `OPENCHAMBER_RUNTIME` env var (already set to
`'desktop'` by both Electron and Tauri shells) to auto-allow remote
custom URLs on desktop. Web deployments retain SSRF protection by
default. The explicit env var override still works for either direction.

Closes openchamber/openchamber#1438

* fix(tts): respect explicit env var override on desktop runtime

## Problem
The previous implementation used `|| isDesktop` which unconditionally
allowed remote URLs on desktop, making `OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS=false`
a no-op. The PR description documented this as supported but it didn't work.

## Fix
Changed precedence logic: explicit env var (set or unset) takes absolute
priority in both directions. When no explicit flag exists, desktop runtime
defaults to allowing remote URLs. This lets operators tighten desktop
deployments with env var =false.

## Test
Added test case: desktop + env var =false → remote URLs denied (8 pass, 0 fail).

Addresses PR review: P1 (env var precedence) and P2 (missing deny test).

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-08 19:15:54 +03:00
nerdosaurusandBohdan Triapitsyn d9b9b56599 Diagram editor pr (#1432)
* feat: add draw.io diagram editor integration

Embed draw.io editor via react-drawio (MIT, zero deps) for inline
editing of .drawio files. Changes auto-save to disk. Includes
inline editor in FilesView with Visual/Source toggle, dark mode
support, template picker for new files, and chat file attachment
integration.

* fix: debounce diagram autosave to prevent reload loop

* fix: ignore watcher-triggered xml prop changes to prevent reload loop

* fix: remove auto-save-to-disk, add manual save button for diagrams

Autosave writes triggered file watcher cascade that reloaded the
draw.io iframe and reset zoom. Replaced with explicit Save button
in the toolbar (floppy disk icon). Editor XML is stable on mount
and ignores watcher-triggered prop changes.

* fix: remove auto-save write from DiagramView, add save button

* fix: hide draw.io save/exit buttons in editor

* fix: also hide save-and-exit button

* fix: brighten save button styling, add saved confirmation

* fix: remove autoSaveStatus toggle on diagram save to prevent toolbar collapse

* fix: add local save confirmation state for diagram button

* fix: remount drawio iframe on theme change, persisting XML across mounts

* fix: clear persisted xml on mount to prevent leaking between files

* fix: initialize dark mode synchronously, preserve edits across theme remount

* fix: auto-focus drawio iframe on mount/theme-change for keyboard shortcuts

* fix: add diagram i18n keys to Traditional Chinese locale

* fix: restore upstream HMR host and LAN address support

* fix: load sub-agent sessions on bootstrap for sidebar visibility

Two-phase session load: first fetch root sessions (for accurate
sessionTotal), then fetch all sessions and include child sessions
(sub-agent delegations). This ensures sub-agent sessions appear
in the sidebar immediately instead of relying on the async global
session store.

* remove opencode-drawio from PR branch

* fix: atomic file writes to prevent concurrent read/write truncation

Three-layer defense against the O_TRUNC race:

1. Write side (server): replace direct writeFile with write-to-temp-
   then-rename. fs.rename is atomic on POSIX.

2. Read side (server): retry up to 3 times with 50ms backoff when
   readFile returns empty but stat reported non-zero size.

3. FilesView client: refuse to save empty draftContent when the
   original fileContent was non-empty.

* fix(dev): clean up orphaned OpenCode processes on Ctrl+C

* fix: allow empty file saves, log warning instead of blocking

Replaces the hard block on saving empty content with a console.warn.
The atomic write + read retry on the server side handle the O_TRUNC
race properly. The previous guard caused a UX regression by silently
preventing users from clearing a file and saving.

* fix: remove time window from sub-agent fallback for live tasks

While a task tool is active, the fallback now matches any session
with the correct parentID regardless of creation time. This allows
late-appearing child sessions to be found when the OpenCode server
is slow or the SSE event pipeline is delayed. The time window is
still applied once the task tool has completed, as a final sanity
check.

* fix: three diagram editor bugs from Greptile review

1. stableXmlRef now resets when xml prop changes — switching
   between .drawio files renders the correct content.

2. Focus effect only runs on mount, not on isDark changes —
   theme toggle no longer steals keyboard focus 600ms later.

3. saveDiagram updates xml state after writing — dirty-check
   guard works correctly for subsequent saves.

* fix: route session.created SSE events to correct directory

Three-layer fix for sub-agent sessions not appearing in sidebar and
inline chat:

1. protocol.js: parseSseEventEnvelope now extracts directory from
   properties.info.directory (where session.created/updated events
   carry it) in addition to properties.directory. WS frames relayed
   to the browser now carry the real directory instead of 'global',
   so child sessions routed to the correct directory store.

2. event-pipeline.ts: same fallback in resolveEventDirectory for
   defense-in-depth when SSE events bypass the WS relay.

3. resolveFallbackTaskSessionId.ts: time window lower bound now
   allows 2s grace before taskStartTime to accommodate server timing
   jitter (child session creation timestamps consistently precede the
   tool's recorded start by ~6-9ms), fixing the 'Open subtask'
   button not rendering in OpenChamber's inline chat.

* fix: sub-agent sidebar visibility, file zeroing guard, inline badge fallback

- Sync watchdog: periodic child session discovery poll (every 15s) detects
  sessions created by other OpenCode instances, triggers parent materialization
- protocol.js: parseSseEventEnvelope extracts directory from
  properties.info.directory for session.created/updated events
- event-pipeline.ts: same fallback in resolveEventDirectory for defense-in-depth
- resolveFallbackTaskSessionId: don't require taskStartTime (cross-OpenCode);
  pick most recent child when multiple idle candidates exist
- readTaskSessionIdFromOutput: parse <task id="ses_xxx"> format from output
- FilesView: reinstate empty-draft guard (block save when draftContent='' but
  fileContent had content) to prevent file zeroing on tab switch

* Fix diagram autosave reload loop

* Highlight drawio files as XML

* Use diff-compatible highlighting for drawio files

* Restore drawio file icon mapping

* Stabilize drawio source preview toggle

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-08 18:50:56 +03:00
Tom RochetteandBohdan Triapitsyn 7b1b3167a4 feat: server-side GitHub search for issue/PR pickers (#1352)
Replace local-only filtering in GitHub issue/PR picker dialogs with
server-side GitHub Search API queries. Search text is sent as a query
parameter to the server, which uses the GitHub Search API
(issuesAndPullRequests endpoint) with repo: qualifiers including fork
network support. Results are debounced at 350ms to respect API rate
limits.

- Add query parameter to GitHubAPI issuesList/prsList interface
- Server routes use Search API when query is present, standard list
  endpoint when absent
- Fork networks handled via repo:owner/repo OR repo:owner/upstream
- PR search fetches full PR details after Search API for head/base/draft
  fields
- Remove local filter memos from all three picker dialogs
- Add debounced search effect with abort controller cleanup
- Update VS Code backend and webview API for parity
- Update search placeholders in all locales

Closes #1350

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-08 15:37:42 +03:00
Bohdan Triapitsyn ef71a997a8 fix: save agent prompt and reload changes reliably
Agent settings now correctly save an emptied system prompt instead of silently keeping the old text.

After saving agent, command, or skill settings, OpenChamber refreshes the updated configuration instead of showing stale values from a short-lived cache.

Reload actions now surface refresh failures consistently while avoiding noisy background errors.
2026-06-08 13:43:45 +03:00
Bohdan Triapitsyn b2b71198ca fix: persist agent permission edits
Prevents permission changes from being overwritten by other agent fields
Writes built-in and custom agent permissions to the correct config source
Refreshes agent state after permission saves
2026-06-08 00:12:53 +03:00
Bohdan Triapitsyn 14e75359a5 fix: forward Inertia headers through preview proxy
Preserves Inertia navigation inside preview panel
Forwards request and response Inertia headers
Adds preview proxy header passthrough tests
2026-06-07 09:53:39 +03:00
Bohdan Triapitsyn e0113c637d feat: support fast worktree-backed session flows
Add a directory-created fast path for worktree creation so session and send flows can continue once the target directory exists while Git attachment and bootstrap finish in the background.

Track bootstrap status explicitly in shared UI contracts, including pending, ready, and failed states. Background watchers now surface failures and timeouts, update stored worktree metadata, and keep web and VS Code runtime behavior in parity.

Move GitHub issue/PR worktree sessions and assistant-answer fork sessions onto the unified send path so provider, model, agent, and variant selections are preserved. The assistant-answer fork dialog can optionally create a worktree outside VS Code.

Make worktree deletion dialogs close after linked-session cleanup while removing the worktree in the background, and clean up failed fast-create artifacts safely without recursively deleting user or agent-written files.

Validation: bun test packages/ui/src/lib/worktrees/worktreeBootstrap.test.ts packages/ui/src/lib/worktrees/worktreeManager.test.ts; bun run type-check; bun run lint.
2026-06-06 23:25:39 +03:00
Bohdan Triapitsyn 341a4ac296 fix: drop WSL OpenCode binary support on Windows 2026-06-05 20:35:53 +03:00
Bohdan Triapitsyn 4548477b14 fix: detect WSL OpenCode installs on Windows
Finds OpenCode in common WSL install paths
Avoids picking Windows shims from WSL PATH
Adds coverage for WSL detection
2026-06-05 19:09:42 +03:00
Bohdan Triapitsyn de0ebbac8b fix: make file tree loading more reliable
Avoid gitignore filtering during folder browsing
Show folder load errors instead of empty folders
Add timeout fallback for gitignore checks
2026-06-05 18:58:50 +03:00
Bohdan Triapitsyn 3e0a105dfe fix: restore OpenCode health compatibility for startup (#1542) 2026-06-05 18:43:13 +03:00
Bohdan Triapitsyn e192359da9 fix: improve startup readiness performance 2026-06-05 15:09:24 +03:00
Bohdan Triapitsyn 16fdff530b fix: improve Windows tunnel support 2026-06-05 15:01:22 +03:00
Bohdan Triapitsyn 4e1560a10e fix: improve auto-accept and Windows opencode upgrades 2026-06-05 13:36:39 +03:00
Bohdan Triapitsyn 04b1425c2e feat: show changed files after completed turns
Add changed-file pills with per-file diff stats
Add a chat setting to disable the feature fully
Avoid changed-file projection work when disabled
2026-06-03 22:11:37 +03:00