Commit Graph
318 Commits
Author SHA1 Message Date
𝖎𝖚𝖑𝖎𝖎𝖆 759af5a77d fix(sessions): recover sessions whose directory disappeared (#3365)
* fix(sessions): keep a shared chat directory until its last session is deleted

Deleting a root chat session removed its managed scratch directory even
when forks, side threads, or subagents still lived in it; OpenCode then
failed every prompt in those sessions with FileSystem.realPath NotFound.
The directory is now removed only once no other known session resolves
to it. The deleted subtree does not count, because the server cascade-
deletes it, and an unloaded global cache keeps the directory instead of
guessing.

Closes #3312.

* fix(sessions): relocate a session whose worktree directory disappeared

A worktree removed outside OpenChamber, by the agent or by hand, left its
sessions pointed at a path that no longer exists: every terminal create
and restart failed with "Invalid working directory" and the tab stayed
stuck, while Git, Files, and prompts kept targeting the dead path.

The terminal server now names that one rejection (TERMINAL_CWD_MISSING)
instead of substituting a directory of its own. The shared UI reuses the
archived-restore fallback for live sessions: a server-confirmed missing
directory moves the session and its stranded subtree to the project's
primary directory through the control-plane move, clears the worktree
hint, re-selects the session, and tells the user where it went. It runs
from a terminal failure and on activation of any session whose directory
is neither a project root nor a managed chat directory; available,
unknown, and failed probes leave everything untouched.

Closes #3338.

* fix(scripts): make oc-dev load again after the changelog cleanup

The changelog cleanup referenced fs.existsSync in a module that imports
existsSync by name and never binds fs, so every oc-dev invocation failed
with "fs is not defined" before reaching its action.

* fix(sessions): probe directory availability on disk, not through OpenCode path resolution

OpenCode's /path never checks that a directory exists: it echoes the
requested path and resolves its project through Git discovery that
swallows errors, so a deleted worktree came back as a valid location and
every missing-directory fallback (draft recovery, archived restore,
session relocation) stayed inert on a real server. The probe now asks
OpenChamber's own /api/fs/list, which stats the path and reports
not-found and not-directory explicitly; anything else stays unknown.

* fix(sidebar): keep a worktree whose directory is gone visible as missing

git keeps a worktree registered after its directory is deleted outside
git and marks it prunable; the list parser ignored that line, so a
deleted worktree looked alive, and nothing in the app asked for a new
listing anyway. The server now reports prunable, the UI keeps such a
worktree in the topology with worktreeStatus missing and a warning icon
on its sidebar group, and relocating a session out of a confirmed-
missing directory raises an in-app topology signal the sidebar
rediscovers on. Dropping the worktree instead would hide every session
that lived there, and a hidden session can never be opened or relocated.
No idle polling is added.

* fix(sessions): never relocate a session to the filesystem root

OpenCode files a directory outside any Git repository under its global
project, whose worktree is the filesystem root. A managed chat whose
directory vanished would otherwise be moved to /. The relocation now
refuses a root destination, and the activation probe recognizes chat
directories through the home-based check as well, so it does not depend
on the chats root having been resolved yet.

* test(sessions): mirror the relocation action in the issue-2039 session-actions mock

session-ui-store now imports relocateSessionFromMissingDirectory, and the
mocked module in this test listed every other action but not that one, so
the file failed on import.
2026-09-05 21:26:21 +03:00
Bohdan Triapitsyn f46fb718c5 fix(chat): recall the current session's prompts by default; tidy the six merged PRs
Input history (#3035) shipped with "All projects" as the default scope and
only recorded prompts sent after the upgrade, so ArrowUp showed other
sessions' prompts and, once switched to "Current session", nothing at all.
Default to the current session and merge the visible transcript's prompts
with the persisted bucket. Existing sessions recall as they did before
#3035, while new prompts keep their attachments and stay recallable after
a revert hides them from the transcript.

Cleanup across #1855, #2297, #3072, #3178, #3035 and #3135: drop the
duplicate poll guards in the file content poller, the zod schema the
VS Code package cannot depend on, a copied file-URL helper and stray
whitespace; move the Enter-to-send strings into the settings namespace;
document OPENCHAMBER_CHATS_DIR, resolve the chats root once on the server
and warm it alongside the other bootstrap calls.
2026-09-05 20:16:14 +03:00
Steffen MächtelandBohdan Triapitsyn 3df97908fe feat(chats): relocate managed chat worktrees via OPENCHAMBER_CHATS_DIR (#3135)
* feat(chats): relocate managed chat worktrees via OPENCHAMBER_CHATS_DIR

Projectless-chat worktrees were hard-pinned to
<home>/.config/openchamber/chats: the UI joined the path client-side,
workspace checks allowed only the config root, and identification matched
the literal path segment. When the OpenCode server runs as a separate
user (UID-separated setups), that root is unreachable — every chat
session answered HTTP 500 (EACCES on the session directory).

The server now owns the chats root. OPENCHAMBER_CHATS_DIR relocates it
(default unchanged: <config root>/chats); /api/fs/home answers
{ home, chatsRoot }; fs workspace checks accept the managed chats root
next to the config root; the client resolves the root from the server
(per-runtime cached, warmed at bootstrap so sync classification sees it)
and falls back to the home join for older servers.

Refs #3130

* chore: trim added comments to local precedent

* fix: forward managedChatsRoot through feature-routes-runtime to registerFsRoutes

* fix(chats): await the root warm-up and keep the legacy chats root owned

Review feedback on #3135:

- bootstrapGlobal now awaits warmChatsRootDirectory, so synchronous
  session classification never sees an empty root cache (relocated
  sessions were grouped as project sessions when the session list
  outran /api/fs/home).
- managedProjectRoots keeps the legacy <config root>/chats entry next to
  OPENCHAMBER_CHATS_DIR, so memory ownership of existing chats survives
  relocation.

* fix(chats): distinguish chats-root fetch failure from older servers

* fix(sync): rehydrate managed chat sessions after the chats root warms

* fix(fs): pass managed roots through the symlink and git-dirs path checks after the main merge

* docs: drop changelog edits; changelog is the maintainer's release-time work

* fix(chats): keep legacy chat directories deletable while the root is relocated

* fix(chats): resolve roots before cleanup and initial session loads

* test(chats): type runtime spies against actual SDK contracts

---------

Signed-off-by: Steffen Mächtel <info@steffen-maechtel.de>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-09-05 19:24:52 +03:00
Matt VisnovskyandBohdan Triapitsyn 1d6b15bc04 feat(chat): persist more composer input history with global or session scope; recallable with up/down arrow keys (#3035)
* feat(chat): persist input history

* feat(settings): configure input history scope

* fix(web): keep input history validation packaged

* fix(chat): preserve input history across tabs

* fix(settings): restore prompt history limit

* fix(settings): keep history deletion warning visible

* fix(i18n): restore Turkish Git empty state translations

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-09-05 19:19:03 +03:00
Matt Visnovsky a0f7e08e88 fix(sessions): restore archived worktree sessions (#3325)
* fix(sessions): restore archived worktree sessions

* fix(sessions): re-move worktree restore roots stranded by a failed move
2026-09-05 17:51:51 +03:00
Bohdan Triapitsyn 65019492e5 fix(sync): guard child-session discovery against overlapping pulls
Child discovery now pages through the whole session list, so one pull can
take longer than the 15s watchdog interval. Two overlapping pulls each read
the store before either commits, so both appended the same newly found child
and the directory store ended up with duplicate rows. Keep one pull per
directory in flight, mirroring the status-poll guard.
2026-09-05 14:45:35 +03:00
Felipe GenéandBohdan Triapitsyn a12b9be443 feat(chat): Comments and review in VS Code, like the OpenChamber desktop app (#1724)
* feat(chat): render code comments as cards instead of fenced text

* fix(vscode): route Add Comment to the active session editor panel

* fix(chat): persist queued inline comments and tighten file-chip path matching

* feat(vscode): comment on code from the editor

* fix(chat): keep attached context in the message and broadcast comment removal

* fix(vscode): hold every pending comment and gate both entry points on the workspace

* fix(vscode): let only the owning surface decide its comment threads

* fix(vscode): drop a comment removed while its delivery was still in flight

* test(vscode): cover the in-flight comment removal guard

* test(vscode): cover comment removal reaching every chat surface

* fix(vscode): give up on a comment the chat never confirmed holding

* fix(vscode): retract a comment everywhere before reporting it discarded

* fix(chat): preserve queued comment cards

* fix: preserve inline comment context across send paths

* fix(chat): preserve command routing with context

* fix(chat): keep unavailable actions on normal send path

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-09-05 12:28:17 +03:00
Matt Visnovsky 4e0eed717d fix: Project action terminal lifecycle (#3287)
* fix(terminal): make command sessions own action lifecycle

* fix(ui): reconcile project action terminal state

* feat(ui): show running project actions in terminal tabs

* feat(ui): run project actions from linked worktrees

* fix(ui): guard project action reconciliation

* fix(ui): scope project action preview fallback

* fix(ui): default project actions to worktrees

* fix(ui): reveal project action terminals

* fix(ui): retain terminal output after snapshot replay

* fix(ui): restore running action terminals on revisit
2026-09-05 12:04:36 +03:00
Bohdan Triapitsyn 7c9fdd1ee5 fix: complete goal resume and comment input follow-ups (#3361) 2026-09-05 10:53:34 +03:00
Leonid c8d8d4a384 fix(sync): paginate child session discovery to avoid >200 truncation (#3271)
Thanks for fixing the child-session discovery cutoff and covering the paginated discovery path with a provider-level regression test.
2026-09-05 10:46:09 +03:00
Leonid ec86d738e2 fix(vscode): stop gating list-derived permission auto-accept on the V2 preflight (#3318)
Thanks for fixing both permission reconciliation paths. The fetchPermission documentation follow-up is maintainer-owned; #3259 remains open.
2026-09-05 02:14:17 +03:00
Bohdan Triapitsyn 34bf631157 feat(queue): queue a message with everything the composer had attached
Queueing captured only the text and files. Context chips (inline comments,
terminal selections, browser annotations, PR comments and checks, quotes,
linked issue/PR/Linear references, pending synthetic parts) stayed in the
composer and only left with the next manual send, so a queued message the
server delivered went out without them and the chips rode an unrelated
message later.

A queued message now carries what the composer would have sent: the text
with its agent mention stripped and file mentions resolved into
attachments, the attached context as structured parts, and the skill
instruction derived from the text. The server delivers those parts in the
composer's order, the VS Code auto-send does the same, and editing a queued
message puts the chips and linked references back. A failed queue restores
the composer completely. Snapshots and broadcasts omit the captured
context like attachment payloads; a take returns it.

Claude-Session: https://claude.ai/code/session_01HB9wdLQoZX2vfyDjwv6Rso
2026-09-04 22:56:27 +03:00
Bohdan Triapitsyn b4a38061bc fix(ui): route answer worktrees from source session 2026-09-04 19:46:21 +03:00
Bohdan Triapitsyn f160f3aac4 Merge remote-tracking branch 'origin/main' into fix/ui-thinking-effort-draft-project-rename 2026-09-04 18:58:08 +03:00
Iuliia IvashkoandClaude Opus 5 9299e2a28d fix(ui): keep an explicit Default effort across the send that follows it
A send carries no effort both when nothing was chosen and when the user picked
"Default", so `sendMessage` could not tell the two apart and recorded the raw
value, which clears the entry. Picking "Default", sending, then switching agent
and back put the settings default back in the picker — the shape of the bug
this branch set out to fix.

`materializeOpenDraftSession` already read the live selection to keep that
distinction on the draft path. Both paths now share `resolveVariantToRecord`,
which prefers `currentVariantSelection.override` while the live selection still
describes the agent and model being sent to, and falls back to the sent value
when it does not.

Three tests go through the real `sendMessage`; two of them fail without this
change. The existing ones seeded the record directly, which is why the send
path was never covered.

Also documents the `oc.chatInput.lastDraftTarget` record in the owning sync
documentation: its three `target` values, what a pre-`target` record and a
removed project fall back to, and why a chat scratch directory is not a project
target.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZuVVgziiLjD81W5vaxdH2
2026-09-04 17:53:11 +03:00
Iuliia Ivashko cb3bc3bd1b merge: bring bulk session archiving and background worktree removal into the thinking-effort branch 2026-09-04 16:54:28 +03:00
Iuliia Ivashko 0ef189f3c8 perf(sessions): archive a worktree's sessions through one server batch
Removing a worktree archived its sessions one SDK call at a time and
then re-rendered the whole sidebar once per streamed session.updated
echo. On a worktree with 121 sessions that meant 14.8s of main-thread
work, 121 requests, and 328 localStorage writes.

- Add POST /api/openchamber/sessions/archive: validates a batch (max
  500 ids, per-request archivedAt), archives sequentially, and reports
  partial failures instead of dropping the batch. VS Code serves no
  such route and answers 501; the shared UI then falls back to the
  per-session path.
- Plan batches from the sessions this client actually holds, live
  directory stores first, so worktree-only sessions still batch.
- Claim (id, archivedAt) pairs before the request and consume the
  matching session.updated echoes, so the server's own confirmations
  no longer fan out into 121 store publications. Runtime-scoped, TTL
  30s, released on response or fallback; non-matching updates pass.
- Make the managed-chats persistence a real trailing debounce instead
  of a 50ms throttle, so a burst of publications coalesces into one
  localStorage write.

Benchmark (121 sessions, production build, real Chrome): 14785ms ->
~1030ms, long tasks 100 -> 1, global store publications 236 -> 1,
persistence writes 328 -> 3.
2026-09-04 16:50:03 +03:00
Iuliia Ivashko 94c90a16b6 fix(ui): reconcile git state after worktree changes 2026-09-04 16:41:39 +03:00
Iuliia Ivashko bde6fed8a2 Revert "revert(ui): restore inherited semantics for Default thinking effort"
This reverts commit 35875e818c.
2026-09-04 14:12:56 +03:00
Iuliia Ivashko 3cd31a090a Revert "fix(ui): stop pinning inherited effort as a session choice on send"
This reverts commit 0a47b306d6.
2026-09-04 14:12:56 +03:00
Bohdan Triapitsyn 07fa83cc72 feat(queue): deliver queued messages from the server
Messages queued while a session is busy used to live in the browser tab and
were sent by that tab once the session went idle, so closing the tab (or
losing the connection) stranded them. The web server now owns the queue:
it persists to <data-dir>/message-queue.json, watches session.status on the
global event hub, re-verifies idleness against OpenCode before sending, and
delivers the head of the queue via prompt_async (or /command for slash
commands) with the model, agent, variant, attachments, and agent mention
captured at queue time. Failed sends stay queued and retry with backoff; a
user abort holds delivery briefly; every change is broadcast so all clients
see one queue.

The shared UI store becomes a projection of the server queue outside VS
Code (hydrate on connect, apply broadcasts, optimistic mutations settled on
the server's copy, one-time upload of locally queued messages from older
builds). Edit / send-now take the full message back from the server. A
UI-driven auto-review run asks the server to hold that session's queue.
VS Code keeps its local queue and foreground auto-send.

Claude-Session: https://claude.ai/code/session_01HB9wdLQoZX2vfyDjwv6Rso
2026-09-04 14:08:08 +03:00
Iuliia Ivashko f2853490de fix(ui): open a chat draft when a chat scratch directory is forwarded
'New session in the current directory' callers (shortcut, menu, composer)
forward the current session's directory even when that session is a
chat. Its managed scratch directory names no project, but it counted as
an explicit project target, so a plus pressed inside a chat session
opened a project draft. Chat scratch overrides now resolve to a chat
draft.
2026-09-04 07:10:23 +03:00
Iuliia Ivashko 0a47b306d6 fix(ui): stop pinning inherited effort as a session choice on send
Sending recorded the effective variant - inherited settings/agent
defaults included - as the session's explicit per-session choice, so the
picker jumped from Default to the inherited effort right after the send
and the session stopped following later default changes. Record only an
explicit picker override when the send reflects the live selection;
sends carrying a captured configuration for another session keep their
captured variant.
2026-09-04 02:16:21 +03:00
Iuliia Ivashko 35875e818c revert(ui): restore inherited semantics for Default thinking effort
The explicit-Default behavior from 77d756aeb (Default = send no effort)
was based on a misdiagnosis: the tester did not know a default effort was
configured in settings, so the settings default applying under Default is
the intended behavior, not a bug. Restores the pre-fix effort resolution,
tests, and documentation.
2026-09-04 02:10:17 +03:00
Iuliia Ivashko 77d756aebb fix(ui): preserve draft and effort selections 2026-09-03 17:32:24 +03:00
Bohdan Triapitsyn 6fa2cb66d0 fix: scope instance-served state to the connected instance
Linear and GitHub logins, quotas, MCP status, skills and agent memory are
served by whichever instance is connected, but each was cached globally or
by directory alone — which two instances can share. Switching instances left
the previous instance's answers on screen and its Linear login usable against
a runtime that has no Linear.

Reset them all through runtimeEndpointReset, each store guarding its in-flight
requests with a generation so a response for the previous instance cannot land
in the new one. The Linear team filter is now persisted per instance: a team
belongs to one workspace, so carrying it across filtered the new instance's
issue list down to nothing.

Usage also waits for the instance to report itself initialised before loading.
Providers report themselves as configured only once the instance can read their
credentials, so a fetch fired at mount answered "nothing configured" for every
provider and cached it — which is why Usage stayed missing from the work-status
panel until Settings -> Usage forced a fresh fetch.
2026-09-03 11:49:34 +03:00
Bohdan Triapitsyn b97f2d05bb fix(chat): restore a message's attached context on revert and fork
Review comments, quotes, terminal selections and annotations were consumed
at send and never put back, so reverting pulled the message into the
composer without the context it was sent with.

Claude-Session: https://claude.ai/code/session_01TwLFeTfBnWdvbg9XyezZQx
2026-08-30 18:32:45 +03:00
Bohdan Triapitsyn 654b3d2441 fix(chat): keep the outgoing conversation still until the next one replaces it
Switching sessions moved the conversation on screen before the swap: the
composer and the status chip followed the live selection and re-shaped a
commit ahead of the timeline, so the pinned outgoing chat jumped; and the
reveal effect re-ran for the outgoing session when its waited flag flipped,
hiding it a few frames before the next one mounted. The chat column now
reads one deferred session, and the reveal runs once per opened session.
2026-08-30 16:54:41 +03:00
Bohdan Triapitsyn b18933f19c feat(chat): surface failed turns and add error diagnostics to the status report
A turn that OpenCode stopped could end with nothing on screen: the
session.error event was only turned into a sidebar badge, its message was
dropped (the notification expected a different shape than OpenCode sends),
and a send that was accepted but never answered looked the same as success.

- The chat shows what OpenCode reported under the last message while that
  turn is the latest one, and names a user message an idle session has left
  unanswered for five seconds.
- The last 20 session errors are kept in memory and listed in the status
  report (Ctrl/Cmd+Shift+L, also `__opencodeDebug.statusReport()`), next to
  rejected sends, the managed OpenCode process's last error and stderr
  tail, and the OpenCode and desktop log file locations.
- The OpenCode health probe hits /global/health instead of a route that
  does not exist, and probe URLs resolve against the page for web runtimes.
2026-08-29 23:22:27 +03:00
Bohdan Triapitsyn d8e223bf49 fix(chat): open a session already at its end, and keep it there
A session opened from the sidebar could land above the bottom, or show a
frame sitting lower and then snap up. The viewport was pinned before the
content was final: the recap note renders once the session record arrives
and grew the footer under the pinned viewport, and on large sessions
subagent task cards grow when their child sessions load, moving everything
above the viewport.

- The recap note holds the timeline reveal until the session record is in
  memory, so it is part of the first finished picture.
- The scroll hook holds the reveal until the viewport is pinned; the reveal
  itself runs once the content height has held still for two frames, with
  one exact pin against the final height (bounded at 300ms).
- Sitting on the end of a session that is not producing output is an
  invariant: content growth re-pins from a MutationObserver in the same
  frame the list writes its layout, so no frame paints with the end out of
  view. Output growth keeps gliding through followEnd, which now glides only
  while the session is working.
2026-08-29 21:17:28 +03:00
Bohdan Triapitsyn c2136ce838 perf(chat): hold the previous timeline while a session loads; fade only when waited
- ChatContainer swaps the timeline on a deferred copy of the selection so the
  active row, URL, and tab commit first. (This part had been overwritten by a
  concurrent edit before the previous commit and is restored here.)
- A session whose messages are not in memory keeps the previous conversation
  on screen for up to 400ms while they load, instead of flashing a skeleton
  between two conversations.
- The reveal fade runs only for a session the user waited for and is 100ms;
  a session that was ready at the click appears in the same frame.
- The sidebar prefetches the two rows on either side of the open session
  150ms after it settles, two at a time, so neighbouring switches are warm.
2026-08-29 17:22:31 +03:00
Bohdan Triapitsyn edfc9779cf perf(chat): make session switching feel instant
Switching sessions ran as one synchronous commit: sidebar highlight, URL,
a full timeline remount with markdown re-parse, and around nine requests,
so nothing changed on screen for 150-250ms after the click.

- ChatContainer swaps the timeline on a deferred copy of the selection, so
  the active row, URL, and tab commit first and the timeline renders behind
  them; selection policy keeps reading the live store value.
- The message fetch starts before the selection is published.
- Sidebar rows stop re-rendering on a project switch: directory-scoped sync
  hooks read the runtime context and a subscribable current-directory source
  instead of the directory-bearing context; the grouping builder reads git
  branches through a ref and section caches key the branches they use;
  descendant ids are keyed by content. Rows per switch went from 73 to 8.
- Markdown skips the async re-render when the settled cached blocks are
  already painted, and mounts synchronously once its lazy module is loaded;
  the module is preloaded at boot.
- A timeline reveal gate holds a freshly opened session at opacity 0 while
  any provisional markdown paint catches up (250ms cap), then fades the whole
  timeline in once, so text, tools, and recap appear together.
- Switch fan-out trimmed: knowledge summary deduped, MCP status refreshed only
  when stale, non-repo directories cached by the git repo check, OpenChamber
  defaults cached briefly, agent memory reused for the same project, goal
  text cached, PWA manifest rebuilt after the switch settles.
- Header tabs snap into the active state and keep the title at the same
  height in both states.
- Prefetch on row press; composer focus moved off the commit.

`bun run profile:switch` records ack/content latency, longest task, and
requests per switch, cold and warm, and compares runs against a baseline.
Measured warm switch: ack 228ms to about 40-60ms, content 228ms to about
100-120ms.
2026-08-29 17:01:15 +03:00
Bohdan Triapitsyn d557c168de feat(ui): attach large text pastes as virtual files (#2619)
feat(ui): attach large text pastes as virtual files
2026-08-29 00:31:04 +03:00
Bohdan Triapitsyn 10634c4512 fix(sync): settle completed turns and finished messages promptly
A lost or delayed turn-ending `session.idle` left the busy spinner up until
the watchdog poll caught it (5-10s). An assistant `message.updated` carrying
`time.completed` now schedules one status check for that session, and
`streaming.ts` stops treating a completed trailing message as streaming.

The check is deferred by 750ms and re-reads the session status when the timer
fires, so the overwhelmingly common case — the turn's own `session.idle`
arriving right behind the completed message — settles on its own and costs
zero extra requests; only a session the store still believes busy spends a
fetch. The poll shares the watchdog's in-flight directory guard, so the
deferred check and the periodic poll cannot overlap on one directory.
Status authority is unchanged: the monotonic pass never lowers status, and an
authoritative resync runs only when the snapshot disagrees.
2026-08-29 00:29:42 +03:00
Bohdan Triapitsyn 63755a8e45 Merge remote-tracking branch 'origin/main' into port-2619
# Conflicts:
#	packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx
2026-08-29 00:26:49 +03:00
Bohdan Triapitsyn 5c26c6ae21 Merge remote-tracking branch 'origin/main' into port-2667 2026-08-29 00:26:15 +03:00
Bohdan Triapitsyn 4f53db17e6 fix(worktrees): protect user changes across ambiguous move failures
Post-merge hardening of the session-to-worktree move (#2998), driven by
review findings on the follow-up pass:

- an ambiguous transport failure (relay abort, timeout) on the
  change-carrying move no longer force-deletes the fresh worktree that
  may hold the user's only copy of their changes; both intent kinds
  surface honest guidance and refresh both directories
- assertSdkSuccess re-tags ambiguous transport errors when wrapping SDK
  failures, so ambiguity classification survives the wrapper on every
  path, matching the prompt-send precedent
- session liveness checks scan all child stores plus the global status
  index, and report unknown (not idle) when no store covers the session
  — an evicted background directory can no longer make a busy session
  look movable
- incomplete-rollback errors carry the changes-may-be-in-destination
  guidance instead of swallowing it
- move-message assembly shared across the three call sites; tests now
  exercise the real ambiguity classifier (extracted to
  send-failure-classification.ts) instead of a hand-mirrored mock
- i18n fallout from the merge train: Turkish gains the 21 worktree-move
  keys, all 12 locales get the hedged ambiguous-failure toast; owning
  DOCUMENTATION.md files record the new contracts
2026-08-28 12:03:55 +03:00
Bohdan Triapitsyn b8465ae133 fix: harden and de-slop the merged contribution batch
Follow-ups promised on merge, plus review findings on the batch itself:

- chat: task-tool output now respects the 512KiB render cap; quick-open
  icon is visible at rest on coarse pointers and reachable by keyboard
  (row keydown no longer swallows inner-button Enter/Space); composer
  inline-code decoration drops the metric-shifting padding; a btw fork
  send carries only the boundary instruction, never the promotion notice
- sync: cascade revert/unrevert aborts busy descendants, busy state is
  read from every child store at the moment of use; rule 9 documents
  redo clearing all descendant revert markers
- electron: renderer recovery keeps memory-eviction (a valid
  render-process-gone reason) and both windows share one
  attachRendererRecovery helper
- vscode: process registry is a thin re-export of the web module
  (provider-env-aliases precedent) with ordered register/unregister
  writes and an awaited close
- server/cli: managed-process registry takes injectable deps (fixes the
  unreaped-orphans ReferenceError), corrupt settings errors name the
  file, getWorktrees test restores console.warn
- tests: module-mock harnesses removed (AgentsSidebar, SettingsView
  mobile focus — behaviors stay live but uncovered, accepted trade),
  QuestionMarkdown asserts rendered DOM
- i18n: German gains the debug-panel request keys, Japanese/German drop
  removed worktree keys, Ukrainian unit spacing fixed
- changelog: Copilot AI Credits entries (main + VS Code)
2026-08-28 02:08:09 +03:00
Bohdan Triapitsyn cdfd28b3ca Merge main 2026-08-28 01:25:55 +03:00
Bohdan Triapitsyn b25b64c000 Merge pull request #3146 from alexandrereyes/feat/undo-subagent
feat(ui): cascade undo and redo to subagents
2026-08-28 01:23:46 +03:00
Bohdan Triapitsyn 655c04aba6 Merge pull request #3151 from yangyaofei/fix-deferred-permissions
fix(sync): keep bootstrap context live for deferred recovery pulls
2026-08-28 01:23:16 +03:00
Bohdan Triapitsyn 3d8b801d52 Merge pull request #2120 from herjarsa/fix/chat-anchor-save-race
fix(chat): save previous-session anchor in microtask with bail check
2026-08-27 23:15:33 +03:00
Alexandre Reyes Martins 56cf45115f feat(ui): cascade undo and redo to subagents 2026-08-27 03:00:30 +00:00
𝖎𝖚𝖑𝖎𝖎𝖆 057a4447a3 fix(ui): preserve default in thinking cycle (#3153) 2026-08-26 20:12:01 +03:00
yangyaofei 02ab4136de fix(sync): invalidate bootstrap context when a newer same-directory run starts
## Problem
Review on #3151: with isCurrent reduced to store liveness, a completed
run's context stays current while a forced same-directory rerun starts,
so a late deferred response could commit over the newer run's state.

## Fix
Track a per-directory run sequence. isCurrent captures the sequence at
run start and stays true across settle (deferred recovery pulls still
commit — the original bug) but flips false the moment a newer run for
the same directory begins. Entries are cleared on directory disposal.

## Validation
- New test: after a forced rerun the first context is retired and the
  second is current. 20 pass in child-store.test.ts.
- bun test src/sync/: 540 pass / 15 fail — same 15 pre-existing on main.
- type-check and oxlint clean on authored lines.
2026-08-27 00:30:39 +08:00
yangyaofei cab2f9adf3 fix(sync): keep bootstrap context live for deferred recovery pulls
## Problem
Pending permission requests (and questions, MCP/LSP/VCS status) vanish
permanently after a page reload. The deferred recovery phase in
bootstrapDirectory — the code that re-pulls permission.list after load —
never executed, so the UI had no way to re-render a card for a request
the opencode server still holds. Fixes #3150.

## Root cause
The deferred phase is scheduled via setTimeout(0) guarded by isStale(),
which maps to the pump's isCurrent(). isCurrent required the run token
to still be present in runningBootstraps, but the pump deletes that
token in .finally() as soon as onBootstrap settles — always before the
setTimeout macrotask fires. The guard was therefore deterministically
stale and the phase was dead code (introduced by 85400459).

## Fix
Make isCurrent a store-liveness check — disposed, generation, and store
identity — instead of run-token ownership. The pump never replaces a
running entry for the same directory (queueBootstrap defers via
rerunRequested), so during the run itself this is equivalent. This
mirrors the existing isCurrent contract in session-message-loader.ts.

## Validation
- New regression test in child-store.test.ts fails on main and passes
  with the fix (isCurrent stays true across the post-settle macrotask,
  flips false after teardown).
- bun test src/sync/: 539 pass / 15 fail — the same 15 fail on pristine
  main; the only delta is the new passing test.
- bun run type-check (packages/ui): pass.
- bunx oxlint on both changed files: no findings on authored lines.
2026-08-27 00:21:43 +08:00
Bohdan Triapitsyn 428854fd78 refactor(sync): replace the session-status setState monkeypatch with an owned API
The global session-status store patched its own setState to derive
active membership for callers replacing statusById — a boundary that
silently trusted any caller passing both fields to keep them consistent.
replaceGlobalSessionStatusById is now the one sanctioned way to swap the
map from outside the event reducers; the runtime-switch reset and the
tests that replaced the map directly go through it, and the patch is
gone. Follow-up to #3126 review.
2026-08-26 03:13:21 +03:00
Bohdan Triapitsyn 90b6776e5b Merge branch 'main' into pr2969-integration 2026-08-26 02:22:29 +03:00
Bohdan Triapitsyn ac880e8e62 Merge main (anchored-turn chat scrolling) into perf/switch-and-scroll
Main replaced the chat timeline scroll engine while this branch was in
flight, which obsoletes two of its subareas and reshapes a third:

- Chat timeline: main's LegendList-based MessageList/ChatContainer win;
  the activation-overscan staircase targeted the removed tanstack path
  (LegendList provides adaptive rendering natively) and is dropped along
  with its test.
- Scroll shadows: main's hook-based masks stay (the virtualized list owns
  its scroll element — there is no wrapper to hand the styling to); the
  viewport-wrapper ScrollShadow rewrite, its index.css replacement, its
  test, and the call-site viewportClassName adaptations are reverted to
  main. The chat OverlayScrollbar keeps this branch's disableHorizontal.
- OverlayScrollbar: the direct-DOM rewrite lands, but binding now follows
  the live container node instead of binding once per ref object — the
  chat scroller remounts on every session switch, and a bind-once
  contract left the scrollbar attached to a dead element.
- Markdown renderer: the detached-DOM cache and warm-block fast path
  merge with main's block-commit reveal (enter cascade), streaming code
  highlighting, and gutter reservation; the per-block reconcile keeps
  both the decoration-refresh path and the reveal cascade.
2026-08-26 00:59:45 +03:00
c_w_xiaohei fa1503a038 perf(ui): narrow session switch fanout
Keep sidebar rows independent from the active sync directory by moving export history loading behind an explicit-directory command.

Make active-project selection preserve project topology and remove activeProjectId from mounted group render props. Reuse a per-session-array ID index so subscription snapshots do not repeatedly scan session lists.
2026-08-26 00:44:14 +08:00