Skip to content

Rebase onto current main and resolve the claude-sessions.test.ts conflict - #1

Open
aldredb wants to merge 61 commits into
swu45:fix/session-title-priorityfrom
aldredb:fix/session-title-priority-rebased
Open

aldredb wants to merge 61 commits into
swu45:fix/session-title-priorityfrom
aldredb:fix/session-title-priority-rebased

Conversation

@aldredb

@aldredb aldredb commented Sep 4, 2026

Copy link
Copy Markdown

This targets fix/session-title-priority rather than main, so merging it here makes siteboon#982 mergeable again without you having to redo the rebase yourself.

It is a merge of current main (c1be241) into your branch, so your two commits stay exactly as they are and remain ancestors — your branch fast-forwards onto this. I added one commit on top for the outstanding review comment.

What conflicted

Only the test file. claude-session-synchronizer.provider.ts merged automatically.

Since 10 July, main grew its own server/modules/providers/tests/claude-sessions.test.ts covering subagent transcripts, agent notifications and prompt editing, which collides add/add with yours. The two sets are disjoint — no shared test names — so I kept both and reconciled only the setup at the top of the file:

  • kept main's withIsolatedDatabase and dropped the near-identical copy from your version, which differed only in the temp-directory prefix
  • carried over your patchHomeDir and writeSessionJsonl helpers
  • added the ClaudeSessionSynchronizer and buildLookupMap imports your tests need

The file now runs 22 tests: main's 10 plus your 12.

The extra commit

CodeRabbit noted twice that extractSessionAiTitleFromEnd no longer describes what the function does — after your fix it scans forward, and it returns a custom-title, ai-title or last-prompt rather than only an AI title. I renamed it to extractSessionTitle and rewrote the JSDoc in the prose style the rest of the file uses. It is a separate commit, so drop it if you would rather keep the original name.

Verification

Check Result
claude-sessions.test.ts 22/22 pass
Full server suite 409 tests, 407 pass, 1 skipped, 1 failure
tsc --noEmit -p server/tsconfig.json clean
oxlint server/ no new warnings

The one failure is conversation search streams title matches before transcript results, which fails at the same assertion in provider.routes.test.ts:119 on unmodified main. It is pre-existing and unrelated to this change.

I also reproduced the original bug independently on the published 1.37.2 before finding your PR, and confirmed it is still live on current main. Your diagnosis and precedence chain match what I arrived at separately.

m061i6 and others added 28 commits July 13, 2026 17:37
Keep the repository lock aligned with the latest resolution already allowed by CloudCLI's declared dependency range. This changes no source code or package manifest.

Constraint: better-sqlite3 12.11.1 does not yet declare Node 26 in its engine metadata
Confidence: high
Scope-risk: narrow
Directive: Do not describe this lock refresh as official Node 26 support until upstream package metadata includes it
Tested: npm ci; native in-memory database smoke; typecheck; client/server build; lint with zero errors on Node 26.5.0 and npm 11.17.0
Not-tested: Platforms outside linux/amd64
Related: CoderLuii/HolyClaude#67

Co-authored-by: Simos Mikelatos <simosmik@gmail.com>
* fix: check CLAUDE_CODE_OAUTH_TOKEN in checkCredentials()

checkCredentials() never checked CLAUDE_CODE_OAUTH_TOKEN, so a valid
long-lived token was ignored and auth status fell through to reading
~/.claude/.credentials.json, reporting false expiry once that file's
short-lived OAuth session cycled.

Add the check in the same priority slot Claude Code itself uses:
after ANTHROPIC_AUTH_TOKEN/ANTHROPIC_API_KEY (incl. apiKeyHelper via
settings.json), before falling back to the credentials file.

Uses method: 'environment', matching the existing convention in
opencode-auth.provider.ts for env-var-sourced static credentials.

* refactor(claude-auth): use a friendlier auth status label

Finding: F5 (cosmetic nit) from cloudcli-upstream-auth-prs-2026-07 audit.
Replaces the raw env var name with a human-readable label in the auth
status email field, matching the friendlier style of the other auth
methods ('Auth Token', 'API Key Auth').

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(claude-auth): cover checkCredentials() OAuth token priority order

Adds coverage for the priority order in checkCredentials(): OAuth token
authenticates independent of a stale credentials file, falls back to
credentials_file when unset, reports unauthenticated on an expired
credentials file, and yields to ANTHROPIC_API_KEY per the documented
precedence order.

checkCredentials() has no dependency on the claude CLI (only env vars
and ~/.claude files), so it's called directly rather than through
getStatus()/checkInstalled() — keeps the test portable across
environments that may not have claude installed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(claude-auth): also check CLAUDE_CODE_OAUTH_TOKEN in settings.json env block

CodeRabbit review on siteboon#979: Claude Code accepts CLAUDE_CODE_OAUTH_TOKEN
from the settings.json/settings.local.json env block, not just
process.env — same as ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN, which are
already checked both ways a few lines above. Checking only process.env
missed users who configured the token there, reporting them as
unauthenticated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix(shell): recover from stale websocket connections

Guard retained PTY ownership so delayed close events cannot detach a replacement socket.

Terminate half-open sockets after a missed pong.

Cover both lifecycle paths with focused tests.

* feat(git-panel): one-click git init plus update and history UX fixes

- show a "Run git init" button in Source Control when the project is not
  a git repository (new POST /api/git/init), hide the branch/refresh
  header controls in that state, and stop logging the expected no-repo
  state as a server error
- track commit-history loading separately so "No commits found" no longer
  flashes while commits are still being fetched
- hide "Create Initial Commit" when the reposi
- auto-refresh the window after a platform update ("This will refresh the
  window in N seconds") and cancel the refresh if the update fails
- handle non-JSON responses from /api/system/update so hosted/Docker
  deployments no longer surface "Unexpected token '<'" when the server
  restarts mid-update

* fix(shell): force restart before reconnecting

* fix: resolve session deep links to owning project and collapse chat error rows

Session routing: /session/<id> URLs could select the wrong project or show
nothing, because the frontend only searched the paginated project payloads
(first session page per project) and fell back to binding the session to
whatever project was selected.

- add GET /api/providers/sessions/:sessionId resolving a session (by app id,
  falling back to the provider-native id) to its metadata and owning project
- resolve unmatched URL sessions thro correct
  project, and renavigate provider-native alias ids to the canonical app id
- keep the selected-project placeholder only as a fallback for unknown ids

Chat errors and commands:

- Bash command rows no longer auto-ex and
  status badge signal the failure and output stays behind the chevron
- collapsed commands truncate to one line again: the global
  `.chat-message code` pre-wrap !imponcate`,
  so the command now renders in a span
- non-Bash tool error results render as a collapsed red row that expands
  to the full error content (new ToolErrorDisplay)

* refactor(chat): pass app session ids to provider runtimes

The chat websocket handler used to substitute the provider-native
session id into the spawn options, so runtimes keyed their process maps
by provider ids and abort/pending-approval lookups had to translate
back and forth. Now the app session id flows through everywhere, and
each runtime resolves the provider-native id itself via the new
sessionsService.resolveProviderSessionId() only where its CLI/SDK
actually needs it (resume flags, resumeThread, OpenCode's token DB).

- chat.send passes the app session id; the unused `resume` flag is gone
- chat.abort addresses runtimes by aps
  aborting a session's first run actually kill the runtime (previously
  abort was a no-op until the provider announced its native id)
- pending permission approvals are tr,
  removing the remap in chat.subscribe
- runtimes key process maps by the app session id when given, falling
  back to the captured provider id fo
- resolveResumeModel now receives the app id, matching how the
  active-model endpoint records session model changes
- run notifications (stopped/failed/permission) carry the app session
  id, matching frontend deep links

* feat(chat): render user messages as markdown and allow image-only sends

- Render user bubbles through the shared Markdown component with
  remark-breaks so typed line breaks are preserved
- Add explicit ul/ol/li styling so list markers render correctly in
  both user and assistant messages
- Open composer image attachments in the fullscreen lightbox on click
- Allow sending/queueing messages with images but no text
- Remove the AI commit message generator button from the git panel

* feat(chat): markdown rendering, image-only sends, chat export, auth check, and UI fixes

User messages:
- Render user messages as markdown with remark-breaks for line break preservation
- Add explicit ul/ol/li styling so list markers render correctly in both bubbles
- Enable sending/queueing messages with images but no text

Chat export:
- Add comprehensive export to Markdown, HTML, and PDF with beautiful formatting
- Export button in top-right corner of messages pane
- Include timestamps and message metadata in exports

Provider authentication:
- Check provider authentication before sending messages
- Show error with instructions: "Not logged in to [provider]. Please log in via
  Settings > Login in CloudCLI, or in the Shell tab."

Image attachments:
- Open composer image attachments in fullscreen lightbox on click (reuse existing
  lightbox from sent images)

UI fixes:
- Move QuickSettingsPanel to app-level (out of MainContent overflow context) to fix
  z-index layering on desktop and mobile
- Set panel z-index to z-[9999] with backdrop at z-[9998] to ensure it appears
  above all content including sidebars and file trees
- Remove AI commit message generator button from git panel source control tab

* fix: ignore subagent and tool-results from scan

* fix: show mcp tool result text type correctly

* fix: disable single-dollar math

- Disable single-dollar math in remark-math to prevent dollar amounts (e.g., $20/psf)
  from being parsed as LaTeX; keep double-dollar display math ($$…$$) for math expressions

* revert(chat): remove pre-send provider auth check

Drop the client-side "Not logged in to [provider]" gate that ran
before sending messages. The backend still validates provider
authentication, so this pre-flight check was redundant and blocked
sends on its own status lookup.

* feat(git): add worktree management with session switching and squash merge

Add a Worktrees tab to the git panel for Crystal-style parallel workflows:

- Create worktrees in sibling <repo>-worktrees/<branch> folders, from a new
  or existing branch, with a live folder preview
- Register each worktree as a linked project ("repo · branch") so chat
  sessions, shell, files, and git all work inside it; one-click switching
- Squash or --no-ff merge a worktree back into the base branch, with
  optional cleanup (remove worktree + delete branch); conflicts are rolled
  back server-side and reported with the conflicting files
- Safe removal: archives the linked project, blocks dirty worktrees unless
  changes are explicitly discarded, never touches the main worktree

Backend: new server/modules/worktrees module (routes + list/create/open/
merge/remove services, barrel-only cross-module imports, node:test suite
with injected git runners) mounted at /api/worktrees.

* fix(git): make tabs scrollable on frontend

* fix(sidebar): keep rename input visible on mobile

* fix(codex): render wrapped tool history

* refactor(worktrees): inject module dependencies

* refactor: move auth, commands, plugins, git to their own modules

* refactor: consolidate shared server utilities

* refactor: move file-tree and voice to their own modules

* refactor(providers): centralize session token usage

Move provider-specific token calculations out of the legacy server entrypoint.
The backend now resolves storage details from the app session record.

Expose a session-id-only provider endpoint and cover every provider path
with focused tests.

* refactor(providers): centralize runtime adapters

Move provider SDK and CLI execution beside each provider implementation so
runtime ownership matches the existing auth, model, session, and MCP layout.

Dispatch runs, aborts, and Claude permission approvals through one typed
runtime service instead of wiring provider-specific functions in server/index.

Keep the stateful runtime entrypoint separate from the lower-level provider
barrel to prevent cycles through session and project services.

* refactor(system): move update endpoint into module

Keep update command selection and process execution out of the legacy server entrypoint.

The route delegates to a tested TypeScript service while preserving the existing API contract.

* refactor(providers): unify runtime ownership

Make each provider own its runtime so providerRegistry is the single resolution source.

Export dispatch through providers/index.ts and centralize structural runtime types.

Remove the redundant runtime registry and runtime facade.

* refactor(server): port entrypoints to TypeScript

Move the Browser Use MCP executable into its feature module.

Route CLI startup through the module barrel and preserve production JavaScript entrypoints.

* chore(repo): codify backend module standards

Share the backend architecture rules with forks.

Require coding agents to apply them consistently during implementation and review.

* fix(providers): discover Codex home skills

Include ~/.codex/skills in user-scoped discovery so installed Codex skills appear in the provider.

* fix: resolve coderabbit comments

* fix: preserve safety across async git workflows

Track request-owned resources so cleanup cannot delete reused checkouts.

Compensate or report post-operation failures so retries do not repeat completed Git work.

Reject unsafe inputs and stale responses before they trigger unintended actions.

Keep exported chat content literal to prevent unsafe rendering.

* fix: keep worktree actions safe and accessible

Serialize worktree operations so requests cannot overwrite each other's busy state.

Use selectable branch defaults and expose actions across touch and keyboard input.

* fix: restore deep-linked session resolution

* fix: refresh auth tokens across realtime clients

* chore: add claude usage plugin

* fix: hide gitignored files from chat mentions

Respect project .gitignore rules when building the @ mention list.

Keep the explorer and other file consumers unchanged.

* fix: show ququed images correctly and improve sidebar archive tab

* fix: prevent repeated skills loading

Key refreshes by stable project values so settings rerenders do not restart discovery.

Keep cached results visible while an explicit refresh is running.

* fix: hide injected skill bodies from live sessions

When Claude invokes a Skill, the CLI injects the entire SKILL.md as a
synthetic user turn. Persisted transcripts tag it isMeta: true, but the
live SDK stream does not. Without a content-level check, the skill body
renders as a giant user bubble during the run and vanishes on reload.

The skill is already represented by the Skill tool call itself, so the
injected body should be filtered as internal content. Filter by the
"Base directory for this skill:" prefix that precedes the markdown.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: discover skills in symlinked directories

* feat: support file attachments across providers

* fix(chat): dedupe image-only user messages

* fix: update chat composer UI

* fix(providers): persist the active model per session

The active model shown by /models was always the provider catalog default
— for Claude, the literal string "default" — because the command resolved
it from the catalog and never looked at the model the composer had sent.
Model selection was global too (one localStorage value per provider), so
reopening an older session showed whatever was last picked anywhere.

Make the model a property of the session, the way the Claude, Codex,
Cursor and OpenCode clients do:

- Add a `sessions.model` column, written when the user picks a model and
  on every chat.send, so a session remembers what it ran with.
- Resolve it in one place. `resolveSessionModel` answers "what is this
  session using?" (session row, then provider session state, then the
  client default, then the catalog default) and backs /models, /cost,
  /status and the composer. `resolveResumeModel` answers "what should
  this run use?" and deliberately skips the provider lookup, which for
  Codex reads a global config that would override the user's pick.
- Layer the session's model over the per-provider default in the
  composer, so switching sessions shows and sends the right model.

Drop the session model-override sidecar (~/.cloudcli/provider-session-
active-model-changes.json), its read/write helpers, the tri-state
{supported, changed, model} shape, and changeActiveModel on all four
adapters. Session state belongs on the session row, where deleting a
session also drops its model.

Also fixes /cost and /status passing arguments shifted by one to the
model resolver (both reported "Unknown"), and the OpenCode lookup
querying its session table with the app id instead of the provider id.

Pre-existing overrides in the old sidecar file are ignored; affected
sessions resolve from provider state until their next send.

* fix(git-panel): make commit confirm message scrollable

---------

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
…iteboon#1074)

buildFileTree recurses into every subdirectory it lists, with no concept
of which directories are legitimate project locations. listProjectFiles
walks the project root to depth 10, and resolveProjectRoot returns the
stored project_path without validating it — unlike the browse endpoint,
which rejects system paths via validateWorkspacePath before walking.

A project whose path is "/" therefore gets swept in full, including
/proc. Traversing live procfs both wastes the walk on thousands of
virtual per-process entries and logs an error for each unavoidable race
(EINVAL on /proc/<pid>/net, ENOENT on /proc/<pid>/task/<tid> when a
thread exits mid-walk), flooding the server log.

Reused the existing FORBIDDEN_WORKSPACE_PATHS list — already the source
of truth for which directories can never be a workspace — to skip
recursion into them, rather than introducing a second, differently
scoped list. The check is an exact path match, so it prunes /proc when
the walk root is "/" without affecting a project that merely contains a
directory named "proc".
…icker (siteboon#1036)

The 'default', 'sonnet', and 'sonnet[1m]' model options in
CLAUDE_FALLBACK_MODELS still describe "Sonnet 4.6" even though the
underlying `sonnet` alias already resolves to Sonnet 5 today (verified:
`claude --model sonnet` reports "Sonnet 5, model ID claude-sonnet-5"). The
Opus entry was already updated to say "Opus 4.8" - Sonnet's labels were
simply missed in that pass.

This is a label-only fix: the `value` fields (`default`, `sonnet`,
`sonnet[1m]`) are unchanged, since those aliases are resolved live by Claude
Code CLI itself and already point at the current model. Only the
human-readable description text was stale, which made it look like Sonnet 5
wasn't available in the picker at all.

Co-authored-by: Haile <118998054+blackmammoth@users.noreply.github.com>
* feat: add provider session ID copy actions

* fix: address provider session ID review feedback
* feat(i18n): add missing zh-CN translations for all namespaces

- Add zh-CN tasks.json (94 keys, previously missing entirely)
- Add voice settings, input queue, and OpenCode translations to chat.json
- Add voice settings, plugin descriptions, desktop notifications to settings.json
- Add file preview and markdown editor translations to codeEditor.json
- Register tasks namespace import in i18n config.js
- Update .gitignore to allow zh-CN and zh-TW tasks.json

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: simplify locale tasks.json allowlist in .gitignore

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Haile <118998054+blackmammoth@users.noreply.github.com>
- Add missing src/i18n/locales/ko/tasks.json (94 keys), translated from
  en/tasks.json, and register it in src/i18n/config.js (ko was the only
  locale in config.js not importing/loading a tasks namespace bundle).
- Add !src/i18n/locales/ko/tasks.json to .gitignore's task-file negation
  list — the blanket "tasks.json" ignore rule had no exception for ko,
  so the new file would otherwise be silently untracked.
- Backfill settings.json (63 missing keys: voiceSettings, notifications.desktop,
  mainTabs.voice/browser, tabs.skills, mcpServers.managed, agents.account.opencode,
  and several pluginSettings entries), and remove one stray key
  (pluginSettings.buildYourOwn) that no longer exists in en.
- Backfill smaller gaps found while auditing all locale files: chat.json
  (17 keys: voice.*, input.queue.*, opencode provider), codeEditor.json
  (5 keys: markdown preview/edit, filePreview.*), sidebar.json (11 keys:
  search.* and tooltips.clearSearch).

All 7 ko/*.json files now have exact key parity with their en counterparts.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Haile <118998054+blackmammoth@users.noreply.github.com>
Adds the Spanish locale with all 7 namespaces (common, settings, auth,
sidebar, chat, codeEditor, tasks) fully translated, registered in the
i18n config and the language selector.
…red (siteboon#1085)

Co-authored-by: Haile <118998054+blackmammoth@users.noreply.github.com>
…iteboon#1115)

The set/restore block around query() was inert for two reasons: the child's
env is snapshotted earlier in mapCliOptionsToSDK() as { ...process.env }, so
a later mutation of process.env never reaches the spawned CLI; and the Claude
CLI does not read CLAUDE_CODE_STREAM_CLOSE_TIMEOUT at all.

The real knob for how long the CLI waits on still-running background agents
after a turn ends is CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS (default 600000 ms,
0 = wait indefinitely). It already passes through via the env snapshot, so
document it instead of setting anything in code. No behavior change.
* feat(sidebar): add recent conversation feed

* fix(sidebar): address recent conversation review

---------

Co-authored-by: Haile <118998054+blackmammoth@users.noreply.github.com>
* chore: upgrade codex sdk

* fix: add rename to mobile session renaming

* fix(sessions): name CloudCLI sessions from initial message

* fix(file-tree): cap project tree traversal at 10,000 entries

buildFileTree walked project roots to depth 10 with no total node
budget, so a broad but valid root such as a populated home directory
could retain gigabytes of tree nodes, starve other API requests, and
crash-loop the server.

Share one remaining-entry budget across every recursive branch and fail
oversized trees with an HTTP 413 FILE_TREE_TOO_LARGE error that asks for
a narrower root or ignore rules. The budget is charged after ignore
filtering, so generated directories and .gitignore exclusions do not
consume it.

Fixes siteboon#1082.

* fix(file-tree): surface file tree load failures in the UI

A failed /files request only logged to the console and left the tree
empty, so the 413 FILE_TREE_TOO_LARGE refusal added in 700c786 rendered
as "No files found / Check if the project path is accessible" — the one
message that does not explain what went wrong.

useFileTreeData now exposes the API's error message, and FileTreeBody
renders it as a warning state ahead of the empty-tree branch. Requests
aborted by a project switch stay silent.

* feat(models): add customizable provider model library

- replace model caching with source-controlled predefined catalogs
- update predefined models for each provider
- persist only custom models in auth.db
- add custom model create, edit, and delete APIs
- add model management to the picker and /models
- keep predefined models read-only
- remove model refresh controls

* fix(chat): persist reasoning effort per session

Prevent stale state from overriding effort selections,
restore effort when reopening sessions, and record
the selected effort on every send.

* fix(models): prevent model library content from being clipped

* fix(codex): do not ignore non-bash tool calls

* fix: resolve coderabbit comments

---------

Co-authored-by: Simos Mikelatos <simosmik@gmail.com>
…n#1078)

Co-authored-by: Haile <118998054+blackmammoth@users.noreply.github.com>
* fix(claude): default background agent wait ceiling to 30 minutes


* fix: respect gitignore in the Files view

Use project .gitignore rules as the authoritative file-tree filter so
legitimate build directory names remain visible. Fall back to the
conventional ignored-directory list when no .gitignore exists.

* feat(claude): keep background work alive after turn completion

- Hold Claude CLI input open for deferred background tasks
- Notify users when background work completes
- Prevent duplicate completion events
- Clean up obsolete background wait configuration

* fix(chat): prevent hidden and unbounded history refreshes

Gate message-history requests by chat visibility, coalesce deferred refreshes, and reconcile bounded latest pages while preserving pagination, realtime messages, and scroll position.

* feat(editor): make file paths easy to copy

Copy the complete path even when its displayed value is truncated.

Provide accessible success feedback after copying.

* refactor(frontend): simplify JSON parsing utilities

Keep malformed tool-input handling at its call site so the fallback is explicit.
Remove generic or unused storage helpers that no longer justify shared abstractions.

* refactor: rename LLM provider logo component

* chore(docs): move readme to docs

* feat(git): allow force deleting unmerged branches

* refactor: relocate shared frontend types

* fix(chat): polish composer navigation and layout

Keep keyboard-selected file mentions visible in long result lists.
Align the queued-message card with the composer edge.

* fix(chat): hide Codex plan envelope tags

* feat(search): prioritize session title matches

Show database-backed session matches before transcript content.

This lets users find conversations by name, even before a transcript is indexed.

* refactor: centralize shared platform constants

* chore: remove unused util

* fix(ui): restore French locale and responsive tabs

Register French resources so language selection no longer falls back to English.

Keep large plugin tab sets reachable on desktop and mobile.

Anchor the tab rail to the right whenever it fits.

* fix: resolve markdown formatting issues

* feat: support mermaid diagram

* feat: add upload to specific directories

* fix(claude): stop orphaned query instances when a run supersedes an active session

addSession now interrupts a different live instance before overwriting its
map entry, and run-loop cleanup only removes/emits for entries it still owns.
Prevents unreachable spinning generators (CPU leak) when an abort races run
setup and a new send takes over the session id.

* fix: addressgithub code quality suggestions

* feat: show session title for browser title

* fix: address review findings
# Conflicts:
#	server/modules/providers/tests/claude-sessions.test.ts
larizzo and others added 30 commits September 7, 2026 11:51
…oon#1265)

The OpenCode provider authenticated any credentials file entry, so a
subscriber who connected OpenCode Go (`opencode-go` in auth.json) counted
as logged in - but the curated catalog had no `opencode-go/*` entries at
all. The connected-provider filter then found no Go options, fell back to
the full catalog, and left Go users staring at a list of models their CLI
rejects while every model they pay for was missing. See siteboon#840, which was
closed without a fix while the bug persists on main.

Add the 27 `opencode-go/<model-id>` entries `opencode models --verbose`
reports, with the reasoning-variant values from the same output wired as
effort metadata so the existing effort picker resolves them through
`--variant`. Labels follow the catalog's existing style; the CLI's
marketing suffixes ("(2x usage)", "(New)") are dropped. `DEFAULT` stays on
the Zen default - the filter already relocates it for Go-only installs.

The docs also list `minimax-m2.5`, but the CLI does not report it, so it
is deliberately left out; the catalog mirrors CLI output.
…on#1249)

Question/answer cards split a multi-select answer on ", " unconditionally,
so a single option whose own label contains ", " ("Yes, always") rendered as
two chips, neither matching an option, both marked (custom).

Match one exact option first and split only when no option matches, which
leaves genuine multi-select answers unchanged.
…eboon#1238)

* feat(chat): recall sent messages with arrow keys in the composer

ArrowUp in an empty composer recalls messages previously sent in the
open chat, newest first; ArrowDown walks forward and finally restores
the draft. History is kept per chat scope (the same session-or-project
key drafts use) in localStorage, capped per scope and across scopes,
and recorded on send, queue, and slash-command execution. The arrows
keep their normal meaning while editing text or navigating the
command/mention menus.

Signed-off-by: Matthew McClintock <matthew@mcclintock.net>

* fix(chat): walk a stable history snapshot during recall

An append from another tab mid-recall shifted what ArrowDown landed on
and could displace the draft restore. Navigation now snapshots the
entries when recall starts and walks that array until recall ends.

Signed-off-by: Matthew McClintock <matthew@mcclintock.net>

* fix(chat): record queued messages before the session-switch return

Switching sessions while a queued message's attachments uploaded hit
the early return after persistence, so the message dispatched later
without ever entering input history. Record it right after persistence,
under the session it was queued for.

Signed-off-by: Matthew McClintock <matthew@mcclintock.net>

---------

Signed-off-by: Matthew McClintock <matthew@mcclintock.net>
Co-authored-by: blackmammoth <118998054+blackmammoth@users.noreply.github.com>
* feat: collapsible branches in the model picker

The picker lists every model of every installed provider in one flat
run - an OpenCode installation alone contributes ninety - so reaching
the provider below means scrolling past all of them.

Each branch now collapses by its heading, with the model count beside
it, and the state is remembered in localStorage. A search opens every
branch for as long as it runs, because a hit inside a collapsed one
would be invisible. A collapsed branch keeps one row ("N hidden - click
to show"): cmdk drops a group whose items are all gone, and a heading
that is no longer rendered cannot be clicked open again.

The rendering moved into its own component rather than growing the
440-line empty state further.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: persist the collapsed branches from an effect

React may run a state updater more than once or discard its result, so a
`localStorage` write inside one is not tied to committed state. The
updater only computes the next set now; an effect on it does the write.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: clear the model search when the picker closes

The search box is controlled, and closing the dialog only flipped `dialogOpen`.
A query stayed behind and came back the next time the picker opened - with
every provider branch expanded, because searching expands them. That undoes
the collapsing this picker exists for.

Closing now clears the query, through one function the four call sites share.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…oon#1223)

Node's explicit module extensions fell through to the default arm of the
language switch, so a .mts or .cts file opened in the editor with no
highlighting at all. The existing `ext.includes('ts')` check already
picks TypeScript for the two new TS extensions and JavaScript for the
other two, so the four cases are all that is missing.

Co-authored-by: Malte Buttjer <claude@buttjer.net>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eboon#1220)

* fix(sessions): keep an archived session archived across a rescan

createSession resets isArchived on every re-index, without checking whether
anything was written. The full rescan runs on each project-list fetch and
selects transcripts by birthtime, so it re-indexes every session created since
the last fetch -- and each of those loses its archive flag. Archiving a session
started since the sidebar was last loaded therefore never sticks, which covers
every session a user archives right after finishing it.

Reset only when the caller reports newer activity: an explicit timestamp that
is newer than the stored one, or no timestamp at all, which the statement
already treats as "now" when it writes updated_at. julianday() parses both the
column-default and ISO forms that occur in the column.

* fix(sessions): treat an omitted timestamp as activity on the upsert path too

Review catch: the ON CONFLICT branch compared excluded.updated_at, which is
already COALESCE(?, CURRENT_TIMESTAMP), so a call that passes no timestamp was
compared as "now" -- and CURRENT_TIMESTAMP resolves to whole seconds, so it is
not newer than a row written in the same second. An archived app-created row,
indexed for the first time without timestamps, therefore stayed archived while
its own updated_at said it was live. Same reasoning as the UPDATE branch, same
`? IS NULL` arm.

Two tests, one per direction, both checked against the unpatched query first:
the omitted-timestamp one fails without this change, and the stale-transcript
one pins that an older transcript is still not activity.

---------

Co-authored-by: Malte Buttjer <claude@buttjer.net>
Co-authored-by: blackmammoth <118998054+blackmammoth@users.noreply.github.com>
…iteboon#1192)

* feat(i18n): connect remaining hardcoded UI strings to translations

Wire user-facing strings that never went through i18n so they respond to
the language setting. Adds a new git namespace for the source control
panel and extends the settings, auth, chat and common namespaces for
skills, browser use, onboarding, the command palette, MCP server views,
the setup form, the version upgrade modal, the folder browser and a few
smaller screens. Template strings become interpolated t() calls, plural
counts use i18next _one/_other forms, labels stored in constant files
are resolved through t() at render time, and commit dates in the history
view follow the active locale instead of hard-coded en-US.

English keys cover every namespace touched here; other languages fall
back to English through the existing fallbackLng setting until their
translations are added.

* fix(i18n): address CodeRabbit full-review findings

- AuthContext: use the auth namespace with stable errors.* keys for
  session/status/login/registration/network messages
- CommandResultModal: translate the modal footer hint and Close button
- MergeWorktreeModal: refresh the untouched default message when the
  locale changes (tracked per worktree)
- gitPanelUtils: FILE_STATUS_LABELS now returns git:status.* translation
  keys, matching FileChangeItem's t() rendering
- BrowserUseSettingsTab: always show localized operation-failure messages
  instead of raw API error text; render the translated runtime-required
  fallback instead of the server's English status message

* feat(browser-use): error-code contract for browserUse API

Server now replies with the structured AppError envelope
{ success:false, error:{ code, message, details } } on every
browser-use endpoint (status, settings get/save, runtime install,
sessions list/stop/delete). Stable BROWSER_USE_* codes let clients map
errors to localized copy instead of rendering raw English API text.

- readApiJson throws ApiRequestError carrying code/details/status and
  accepts both legacy string and structured error envelopes
- BrowserUseSettingsTab and BrowserUsePanel translate failures through
  code->i18n-key tables with localized per-operation fallbacks
- add en browserUse.installFailed key used by the panel mapping

* fix(api): preserve legacy top-level details in readApiJson

Legacy envelopes ({ success:false, error:'msg', details }) lost their
top-level details because payload was empty for string errors. Fall back
to data.details and cover structured, legacy and bare envelopes with
tests.

* chore: align package-lock.json and package.json with upstream main

The PR carries no package-lock.json changes (reviewer request): the lockfile
now matches origin/main exactly.

---------

Co-authored-by: blackmammoth <118998054+blackmammoth@users.noreply.github.com>
…eboon#1162)

The project and session delete dialogs in `SidebarModals.tsx` render five
translation keys that exist in none of the locale catalogs:

- deleteConfirmation.archiveProject
- deleteConfirmation.archiveSession
- deleteConfirmation.archiveSessionNotice
- deleteConfirmation.archivedSessionNotice
- deleteConfirmation.deleteSessionPermanently

Each call site passes an inline English default, so the keys never surface
as missing at runtime — they silently render English in all 11 languages.

The effect is worst where it matters most. Both dialogs offer a safe action
and a destructive one side by side. The destructive button uses
`deleteConfirmation.deleteAllData`, which *is* translated everywhere, while
the safe "Archive" button falls back to English. A non-English user reading
the project dialog sees a localized "Delete all data permanently" next to an
English "Archive project", which is precisely the choice the dialog exists
to make clear.

Adds the five keys to all 11 locales, following the pattern of siteboon#896.

Note: `removeFromSidebar`, `allConversationsDeleted` and `cannotUndo` are
translated in every catalog but no longer referenced by any component — they
appear to be the pre-refactor names of these same strings. Left untouched
here to keep this change additive; happy to remove them in a follow-up.
…oon#1274)

listPluginSkills read a plugin's commands/ or its skills/, whichever it
found first: the existence of a commands folder ended that plugin's turn,
so a plugin shipping both contributed only its commands. The commands
reader also takes .md only, so a plugin whose commands are in another
agent's format lost both halves at once -- nothing matched, and the
`continue` walked past the skills sitting beside them. The CLI offers both
halves for the same plugin, so those skills work in the terminal and are
absent from the slash menu, with nothing to say why.

Read both folders. The skills branch already skips a plugin with no
skills/, and the menu dedupes by command, so a name present in both places
is still listed once.

Fixes siteboon#1273

Signed-off-by: Liran Funaro <liran.funaro@gmail.com>
…on#1159)

`getProviderModels` resolves to a `ProviderModelsDefinition` - `OPTIONS`
and `DEFAULT` - but the agent route read `.models` off it, so both
catalog handles came back undefined. A codex or opencode run that named
no model then died on `codexModels.DEFAULT` with a TypeError before the
provider was ever started, and the route reported it as a 500.

The route's own test mock encoded the same imaginary wrapper, which is
why nothing caught it. The mock now returns the real shape, and a new
test drives the codex path with no model and asserts the catalog default
reaches the runtime.
…ion model (siteboon#1207)

* fix(claude): ignore <synthetic> model placeholder when resolving session model

Claude Code stamps locally-synthesized rows in session JSONL files (API-error
placeholders and similar) with model: "<synthetic>". The session-model scan in
readClaudeSessionModelFromJsonl walks the JSONL backwards and takes the first
event carrying a model field, so when the last row happens to be such a
placeholder, "<synthetic>" is surfaced as the session's active model. The UI
then adopts it, sends it back as the model for the next turn, and the SDK
issues a real API request with model "<synthetic>", which the upstream API
rejects with 404 model_not_found.

Treat angle-bracketed values as placeholders and skip them, falling back to
earlier events or the provider default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(claude): skip placeholder hits inside content-part scan

Move the placeholder filter into extractClaudeModelFromMessageContent so a
placeholder in an earlier content part no longer stops the scan before a
later part carrying the real model tag. Add regression tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(claude): filter placeholders per candidate inside text extraction

A placeholder "set model to <synthetic>" stdout hit no longer shadows a
real <model> tag later in the same text. Filtering now lives at each
candidate site in extractClaudeModelFromTextContent, so the content-part
scan needs no extra checks. Regression tests cover both content shapes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…wn (siteboon#1157)

A Conversations row was a link and a chevron: you could open the session
and nothing else. Renaming it, copying its provider session id, forking
it or archiving it meant finding it again under its project. This gives
that list the controls the Projects list already has, from one component
rather than a second copy of the markup.

Re-ported onto the layout siteboon#1206 landed, which moved
src/components/sidebar to src/modules/sidebar and flattened
view/subcomponents. Nothing of the previous version survived as a diff,
so this is that change rewritten against the new tree, with two
differences worth naming:

- fork is included. It did not exist when this was first written, and it
  is the third action the Conversations row was missing. The fork path
  reads only a session's id, provider and owning project, which is
  exactly what a recents row knows, so no session object is invented.
- PROVIDER_LABELS moves to utils/sidebarProjectFormatting, beside the
  other row formatting, rather than being exported from a component.

The shape is unchanged from the version that was reviewed down to ~116
net lines: the mechanism is extracted, not duplicated.

  SessionOptions        the options menu and the inline rename that
                        replaces it, with the outside-click dismissal
                        that belongs to it
  useProviderSessionIdCopy
                        the id behind "copy id": fetched when the menu
                        opens rather than per row, dropped when it
                        closes, sequenced so a late reply is discarded
  SessionRowActions     the nine members a row needs, named once;
                        SidebarProjectListProps composes it rather than
                        restating it, and the call site passes one prop

SidebarSessionItem is 208 lines lighter for giving those up; the
Conversations row spends 103 to gain all four actions and two states.

Callers keep only what is genuinely theirs. The Conversations row shows
the amber attention dot and the processing spinner, but not the green
"touched recently" dot — in a list ordered by recency it would be on
nearly every row and say nothing. A row whose project is unknown gets no
rename, since a rename is keyed by project; it is withheld rather than
guessed.

Also fixes a bug the second list exposed: nothing refetched the recents
feed after a rename or a delete, so the row you had just acted on kept
its old title or stayed in the list. Both now patch in place, which also
preserves the pages loaded past the first that refetching page zero
would discard.

Build, both typechecks and lint are clean; 380 client tests and 394
server tests pass, including six new ones asserting what the
Conversations row resolves for the shared controls.

Signed-off-by: Liran Funaro <liran.funaro@gmail.com>
Co-authored-by: blackmammoth <118998054+blackmammoth@users.noreply.github.com>
* feat(codex): expose GPT-6 Astra model

* chore(codex): upgrade runtime for GPT-6 Astra
* feat(codex): expose GPT-6 Astra model

* chore(codex): upgrade runtime for GPT-6 Astra

* fix(codex): replace unsupported default approval policy

Use on-request to prevent startup failures while preserving workspace sandboxing.

Update permission descriptions and test both new and resumed Codex sessions.

---------

Co-authored-by: Simos Mikelatos <simosmik@gmail.com>
Adds realjustinwu/CloudCLI-GLM-Usage to the unofficial plugin
recommendations: live GLM/Zhipu coding-plan quota gauges plus 30-day
token history parsed from local Claude Code transcripts.

Co-authored-by: Claude <noreply@anthropic.com>
…eboon#1312)

Make the project header sticky at the top of the scroll area when a
project is expanded, so the header and its session list are visually
isolated: the header stays fixed below the search bar and only the
sessions scroll beneath it.

- Wrap the project header in a sticky top-0 z-10 container
- Give the sticky header a solid bg-background so scrolling sessions
  do not show through
- Restructure desktop Button background logic so the selected, starred
  and default states each provide a solid background for sticky mode

Co-authored-by: blackmammoth <118998054+blackmammoth@users.noreply.github.com>
…iteboon#1277)

* fix(codex): restore user prompts from canonical typed rollout rows

Recent Codex releases write real user prompts as typed `item_completed`
rows carrying `UserMessage` items; the legacy `event_msg/user_message`
fan-out no longer exists in those files. The history reader only created
user rows from the legacy event, so after a refresh the transcript showed
assistant and tool activity without the user's own prompts.

The reader now also builds user rows from typed `UserMessage` items,
keeping the same per-turn anchoring rules and carrying local/pre-encoded
images through to the transcript. Wire-level `response_item` rows are
still deliberately ignored for prompts because they can carry internal
content. When one file mixes typed and legacy copies of the same prompt
(for example across a format boundary), the typed row wins and the legacy
row is dropped so no duplicate bubbles appear.

Verified against a real 0.153 rollout: user prompts from typed rows render
identically to legacy prompts, alongside unchanged assistant/tool history.

* fix(codex): dedupe typed and legacy prompts per turn

The previous cleanup removed every legacy user row once a file contained
any typed item. A rollout spanning a format boundary can have legacy-only
turns before the typed era and typed-only turns after it; deleting all
legacy rows lost the earlier prompts even though no typed copy existed.

Track each legacy user row's turn id and drop it only when a typed
UserMessage row exists for that same turn. Legacy-only turns and rows
without an attributable turn are preserved. Regression test covers the
mixed-era file with both prompt sources.

* fix(codex): simplify typed user prompt restoration

Codex legacy and paginated rollouts are distinct formats and never mix in one file, so the typed-vs-legacy deduplication was defensive code that could only be exercised by synthetic fixtures. Remove that state and post-read cleanup, and tighten the typed item reader and its integration test to the minimal behavior needed: restore UserMessage text and image inputs while ignoring wire-level user response items.

---------

Co-authored-by: blackmammoth <118998054+blackmammoth@users.noreply.github.com>
…teboon#1333)

* fix(opencode): compose the provider prefix into session model ids

* test(opencode): keep the cleanup boundary around database initialization
…iteboon#1303)

Both runtimes forward a stderr chunk to the chat verbatim as an error
message. The CLIs style that output for a terminal, so the escapes reach
the browser as literal text:

    !  Error
    �[93m�[1m! �[0m�[0mpermission requested: external_directory

Clean the text before it is surfaced, and drop a chunk that turns out to
have been styling only rather than showing an empty error bubble.

Four private copies of an ANSI stripper had already grown in the backend,
each with a different regex and none of them exported. Move one
implementation to `server/shared/utils.ts` and point every caller at it.
The shared pattern is the union of what those copies covered: OSC, CSI in
both the `ESC [` and 8-bit introducer forms, and the remaining ECMA-48
escape sequences. Covered by unit tests.

Co-authored-by: blackmammoth <118998054+blackmammoth@users.noreply.github.com>
Model replies that reference an image by workspace path, such as
![cat](imagenes/cat.png) after generating or screenshotting a file, rendered
as a broken image: react-markdown emitted a bare <img src> that the browser
resolved against the web origin, and the project files route needs the auth
header anyway.

Add an img override for chat markdown that fetches workspace paths through
api.readFileBlob (the same authenticated route the file tree and attachment
thumbnails use), shows the blob inline and expands it in the existing
ImageLightbox. Web, data and blob URLs pass through untouched; a missing file
falls back to the alt text. ChatInterface provides the selected projectId via
a small context so every Markdown call site in the transcript resolves paths
against the right project.

Co-authored-by: pmoncadaisla <pmoncadaisla@users.noreply.github.com>
Co-authored-by: blackmammoth <118998054+blackmammoth@users.noreply.github.com>
…lded in (siteboon#1295)

* feat(chat): say a compaction out loud, as one row with its summary folded in

Compaction is the most expensive thing a long session does without being
asked, and the conversation said nothing about it: both records that
describe one are `system` events, which normalize to nothing. What was left
was the summary itself, a 15-25 KB assistant bubble arriving with nothing in
front of it to say what it was.

The server turns both into one ordinary assistant row -- everything they
say is in `content`, so a client that knows nothing of the new `compact`
field still reads the sentence:

    Compacted · manual · 335k → 10k tokens · 2m 22s
    Compacting conversation…
    Compaction failed: context still too large

The client draws that row, folds the summary into it behind a disclosure,
and reconciles the three rows a compaction actually produces:

- the boundary (the numbers) and the flagged summary arrive in opposite
  orders live and from history, so the fold works from either direction;
- the CLI writes the summary twice, once flagged and once into the live
  stream, and the unflagged copy is dropped wherever it landed;
- the CLI also acknowledges compaction with the bare word `Compacted`,
  which is dropped only when a real row exists to replace it -- against a
  server that reports none it is the only trace there is.

Closes siteboon#1292

Signed-off-by: Liran Funaro <liran.funaro@gmail.com>

* fix(chat): fold the summary into a copy of the row, not the row itself

The row a summary folds into can be one the projection cache handed back,
which is shared across renders -- and a memoized row that keeps its
identity while its content changes does not redraw. Replace it instead.

Signed-off-by: Liran Funaro <liran.funaro@gmail.com>

* fix(chat): supersede the row directly above, and never a user row

Three things the review caught, all real.

A `running` row was left standing under the row that said how the
compaction ended, because only a summary-only row was being replaced. Both
were then visible, live. A terminal row now replaces a running one — and
carries over the summary if one had already folded into it, which is the
order running → summary → boundary that live delivery produces and that
would otherwise have dropped the summary on the floor.

The stray-copy scan could take a *user* row that happened to repeat the
summary text. Assistant rows only.

The orphan-summary scan reached backwards past intervening rows, so a
compaction that arrived later could adopt a summary belonging to an earlier
one. Only the row directly above is superseded now; a compaction further
back belongs to itself.

Four cases added: running → done, running → failed, running → summary →
boundary keeping the summary, an orphan left alone behind an ordinary row,
and a user echo of the summary text surviving. Two of them fail without the
first fix.

Signed-off-by: Liran Funaro <liran.funaro@gmail.com>

---------

Signed-off-by: Liran Funaro <liran.funaro@gmail.com>
Co-authored-by: blackmammoth <118998054+blackmammoth@users.noreply.github.com>
Co-authored-by: blackmammoth <118998054+blackmammoth@users.noreply.github.com>
* fix(opencode): hide child sessions

Signed-off-by: Jesper Derehag <jderehag@hotmail.com>

* perf(opencode): prune child sessions once

---------

Signed-off-by: Jesper Derehag <jderehag@hotmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.