Commit Graph
3263 Commits
Author SHA1 Message Date
Serhii Dziupin e2fb4f8f21 Merge pull request #2665 from makeittech/fix/ope-178-yaml-frontmatter
fix(web): parse agent frontmatter as leniently as OpenCode
2026-08-06 10:07:17 +03:00
Serhii Dziupin 5964a7d8ba Merge pull request #2698 from makeittech/feat/gh-2583-markdown-loops
feat(tasks): support markdown scheduled-task loops in .agents/loops
2026-08-06 09:57:49 +03:00
makeittech 0a4fd7c5fb docs(tasks): add loops quick-start to the scheduled-tasks page
User-facing onboarding for markdown loop tasks: where .agents/loops
files live (project + user scope), a copy-paste sample file, the
frontmatter field table, and the behavior contract (file authoritative,
off by default, rename/malformed semantics, run-now still available).
Also lists the cron schedule type in the UI task creation steps, which
the page previously omitted.
2026-08-06 09:56:11 +03:00
makeittech 9b6b90504c fix(tasks): cover syncProject wiring and allow deleting orphans after file removal
Review follow-up:

- runtime.test.js: add syncProject wiring tests with a real temp-dir
  project and real project-config runtime — asserts reconcileLoopTasks is
  driven with the discovered loops when the project path is known (task
  created, nextRunAt computed) and that plain listing is used when the
  path cannot be resolved (reconcile not called).
- service.js: DELETE on a loop-owned task is rejected with a 400 only
  while its loop file still exists on disk; once the file is gone the
  orphan task can be deleted directly instead of waiting for the next
  reconcile. Tests use real temp files for both branches.
- DOCUMENTATION.md: delete semantics updated accordingly.
- PR description refreshed for the final HEAD (test counts, reconciliation
  contract, evidence wording).
2026-08-06 09:49:31 +03:00
makeittech 59a6c1b70d fix(tasks): guard loop name length and surface loop ownership in the UI
Review follow-up:

- Reject loop files whose frontmatter name exceeds MAX_TASK_NAME_LENGTH
  (80): task names are clamped at storage time, so a raw name longer than
  the limit could never match the stored task identity. The file is treated
  as malformed (definition: null) instead of creating an unreachable
  definition; MAX_TASK_NAME_LENGTH is now exported from project-config.js
  and shared with loops.js.
- Surface loop-sourced tasks in the scheduled-tasks dialog: tasks carrying
  loopFile show a 'Managed by loop file <path>' note, and the enable
  toggle / edit / delete actions are disabled with an explanatory tooltip,
  since the file remains authoritative and would revert any such change.
  run-now stays available. New locale keys added to all 11 message files
  (i18n parity test enforces exact key sets).
- ScheduledTask type gains an optional loopFile field (additive, unknown
  to older clients).
2026-08-06 09:38:18 +03:00
makeittech 359225d363 fix(tasks): harden loop reconciliation against renames and malformed files
Review fixes for the markdown loop feature:

- Loop-owned tasks now adopt by loop file path, not task name, so renaming
  a loop (frontmatter name or UI rename) renames the task in place instead
  of leaving a stale duplicate that keeps running the old definition;
  orphan duplicates of the same file are unscheduled.
- Unparseable loop files are reported to the scheduler as
  definition:null entries: a task whose file still exists is kept with its
  last good definition, and only a genuinely removed file unschedules it.
  Transiently malformed files (mid-edit, bad merge) no longer delete tasks
  or their runtime state.
- Adoption preserves UI-only execution fields (goalEnabled, goalTokenBudget,
  permissionAutoAccept, variant) that the portable format does not define.
- DELETE on a loop-sourced task now returns 400 with guidance to remove the
  loop file, instead of being silently undone by the next reconcile.
- Loops default to enabled: false; discovery of repository content never
  auto-executes scheduled sessions unless the file explicitly enables them.

Regression tests for each fix; DOCUMENTATION.md updated.
2026-08-06 09:28:31 +03:00
ChangeHow 23253b9481 fix(ui): refine contextual shortcut interactions 2026-08-06 14:18:15 +08:00
ChangeHow 45a792d657 feat(ui): show platform-specific shortcut labels 2026-08-06 14:18:15 +08:00
ChangeHow 8d968f3d71 fix(ui): enforce shortcut conflict rules 2026-08-06 14:18:15 +08:00
ChangeHow 669f1603d4 fix(ui): handle IME prefixes and select shortcut conflicts 2026-08-06 14:18:15 +08:00
ChangeHow 6420460dfc fix(ui): refine shortcut and recent session interactions 2026-08-06 14:18:15 +08:00
ChangeHow aba10476c6 refactor(ui): enforce shortcut registration IDs 2026-08-06 14:18:15 +08:00
ChangeHow a5d5d07d3e feat(ui): add session list shortcut 2026-08-06 14:18:15 +08:00
ChangeHow 51fd947de8 refactor(ui): separate shortcut configuration 2026-08-06 14:18:15 +08:00
ChangeHow bb25b68657 refactor(ui): centralize shortcut schema 2026-08-06 14:18:15 +08:00
ChangeHow 4c5421a7af feat(ui): redesign shortcut settings 2026-08-06 14:18:15 +08:00
ChangeHow 1b9da5f9fa feat(ui): add draft target shortcut sequences 2026-08-06 14:18:14 +08:00
ChangeHow ab3ccf395d refactor(ui): register application shortcuts centrally 2026-08-06 14:18:14 +08:00
ChangeHow 82f099be0a feat(ui): add shortcut registry and sequence dispatcher 2026-08-06 14:18:14 +08:00
Serhii Dziupin 352cd2e1e2 Merge pull request #2699 from makeittech/fix/gh-2577-stale-running-ui
fix(sync): finalize tool parts orphaned by an interrupted turn after settlement (#2577)
2026-08-06 09:00:54 +03:00
Serhii Dziupin 4fa753773e Merge pull request #2707 from openchamber/feat/file-tree-depth-limit-497d
fix(fs): keep file-tree list paths through symlinks (#2627)
2026-08-06 08:33:05 +03:00
Serhii DziupinandSerhii Dziupin ee57088dfe fix(fs): keep list paths in requested space through symlinks
Closes openchamber/openchamber#2627

Listing a directory through a workspace symlink was returning realpath
entry paths. The file tree then rejected nested expand toggles because
those paths fall outside the workspace root.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-05 17:40:01 +00:00
Serhii Dziupin 22766fe0ac Merge pull request #2706 from openchamber/feat/terminal-escape-key-behavior-ba7f
fix(ui): Escape in terminal reaches PTY instead of closing panel
2026-08-05 20:21:09 +03:00
Cursor AgentandSerhii Dziupin 938e58868f fix(ui): let Escape reach the terminal PTY from the context panel
The desktop ContextPanel captured Escape on the panel aside and closed
the whole pane before ghostty-web's bubble-phase listener could forward
the key. Skip closing when the event target is inside the terminal so
apps like Vim can leave insert mode.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-05 17:11:41 +00:00
Serhii Dziupin 23a677b45e Merge pull request #2679 from makeittech/feat/gh-2364-git-changes-count
feat(ui): show the changed-files count badge on the Git rail surface
2026-08-05 17:06:24 +03:00
Serhii Dziupin a0436b3aa8 Merge pull request #2678 from makeittech/feat/gh-2447-focus-after-context
feat(chat): refocus composer after adding message to context
2026-08-05 16:40:32 +03:00
Serhii Dziupin f7157d3137 Merge pull request #2487 from pascalandr/fix/2405-settings-persistence
fix(settings): persist collapsed message preference
2026-08-05 16:39:05 +03:00
Serhii DziupinandSerhii Dziupin 341b4b45c8 fix(ui): stop painting git activity dots on non-git rail surfaces
The badge PR inverted showActivityDot to !== 'git', so editor/terminal/diff
picked up the blue activity dot whenever git had changes. Git already shows
a numeric badge; other surfaces should stay quiet. Also split the count
aria/tooltip strings into singular/plural keys.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-05 13:26:46 +00:00
Serhii Dziupin 856ab452dc docs(server): document onOpenCodeRestarted lifecycle dependency 2026-08-05 14:16:18 +03:00
Serhii Dziupin e3202df037 fix(sync): finalize tool parts orphaned by an interrupted turn after settlement
When a managed OpenCode process dies mid-turn (crash, health-check
restart), the persisted turn never settles: the trailing assistant
message has no time.completed and its tool parts stay pending/running
forever — the server never finalizes them (anomalyco/opencode#19023).
The existing settle-triggered tail refresh refetches the same stale
records, so the UI kept running tool timers and working styling
indefinitely (#2577).

Now, when a session is authoritatively settled (session.idle/
session.error event, or an authoritative status snapshot lowering a
previously busy session) and the trailing assistant message is still
unfinished with active tool parts and no pending question/permission,
the orphaned parts are finalized locally as error/"Interrupted" with
an end time — the same shape OpenCode itself writes for cancelled
tools. The mark is gated on an explicit idle status (absent status is
"unknown", never judged), never applies while busy (including
question/permission waits), and a later terminal event or refresh
supersedes it while a stale running refresh cannot regress it (the
reducer and materializer already preserve final statuses).

Fixes #2577
2026-08-05 14:15:03 +03:00
Serhii Dziupin 2fcfe511a5 feat(tasks): support markdown scheduled-task loops in .agents/loops
Adds markdown-based scheduled-task definitions ("loops") discovered from
.agents/loops/*.md (project scope, ancestor directories up to the
worktree root) and ~/.agents/loops/*.md (user scope), mirroring the
skills discovery pattern.

File format: YAML frontmatter (name, schedule cron, enabled, model as
provider/model, optional agent/timezone) plus the markdown body as the
execution prompt. Discovery and parsing live in
scheduled-tasks/loops.js; project-config gains reconcileLoopTasks which
runs inside the project write lock on every syncProject:
- identity by task name; a loop takes over a matching task, preserving
  its id and runtime state (markdown wins on conflict with JSON)
- tasks whose loopFile is gone are unscheduled; JSON tasks are never
  removed
- new loops are created under deterministic loop:<scope>:<name> ids
- project scope shadows user scope on name collisions
- malformed files are skipped with a warning and never block valid ones

Runtime state stays in the project config/state store; it is never
written to the markdown files. Module documentation updated with the
file format and reconciliation rules.

Fixes #2583
2026-08-05 14:11:28 +03:00
Serhii Dziupin 17d5b90d83 fix(ui): add in-document search to the Markdown file preview
The rendered Markdown preview had no way to search: the Electron desktop
shell implements no find-in-page at all, and CodeMirror's search panel only
exists in edit mode, so Ctrl/Cmd+F in the preview was a dead shortcut (web
browsers happen to find plain-DOM text natively, but desktop does not).

Adds a compact find bar for the rendered preview (Ctrl/Cmd+F or the search
button): case-insensitive match highlighting with a live count, Enter /
Shift+Enter and arrow buttons to navigate matches, Esc to close. Matches
are wrapped in <mark> elements and re-applied via MutationObserver when the
markdown renderer re-morphs the container (theme/content changes); svg
(mermaid) and script/style text is skipped. The pure match-range logic is
unit-tested.

Fixes #2401
2026-08-05 14:05:12 +03:00
Serhii Dziupin 13f6a0280d fix(server): rebind message-stream upstreams after a managed OpenCode restart
When the managed OpenCode process exits but a server survives on the old
port (Windows: killProcessOnPort is a no-op, so the orphaned process tree
keeps the port), restartOpenCode() times out waiting for the port and
spawns a fresh server on a NEW port. HTTP/proxy traffic follows the new
port, but the global message-stream hub's upstream SSE reader stays pinned
to the old server's /global/event stream — that connection never closes —
so new events never reach the UI and the chat stops updating until the
app is restarted (#2638).

Lifecycle now fires an optional onOpenCodeRestarted hook after a
successful managed restart; index.js wires it to the new
messageStreamRuntime.rebindUpstream(), which restarts the shared hub
(its reader re-dials buildOpenCodeUrl → the current port) and closes
directory-scoped sockets so their per-connection readers rebuild against
the new port. External servers are untouched (their port cannot change).

Fixes #2638
2026-08-05 13:59:17 +03:00
Serhii Dziupin 264fc16f2c fix(ui): keep the selected model when switching agent modes
Switching between Build and Plan modes reset the model selector to the
settings default because setAgent fell through to the settings-default
fallback whenever the target agent had no saved override, and the
explicit-switch path in ModelControls force-applied the agent's default
model, overwriting any per-agent override.

setAgent now keeps the current model selection when the user has a live
manual selection and the target agent configures no model of its own,
and the explicit-switch handler no longer clobbers saved per-agent
overrides with the agent default. Startup and pin behavior are
unchanged: the settings-default and agent-pin cascade still applies
when no manual selection exists yet.

Fixes #2531
2026-08-05 13:50:23 +03:00
Serhii Dziupin f64c4a74af fix(chat): do not hijack ctrl/cmd+digit while typing in an input
The numbered context-surface switcher (mod+digit) fired even while focus was
in an editable target, stealing the browser's own tab-switching chord and
opening the changes pane mid-typing (issue #2503). Guard the digit branch
with an editable-target check (input/textarea/contenteditable, covering the
CodeMirror composer) so the chord keeps its normal meaning while the user
types; surface switching still works from any non-editable focus, and the
shortcut remains rebindable/unassignable in Settings.

Fixes #2503
2026-08-05 13:49:12 +03:00
Serhii Dziupin 8a85073261 fix(server): forward Small Model override to managed OpenCode config
OpenChamber's Settings → Chat → Small Model override only fed OpenChamber's
own /api/small-model/generate utility service; it never reached the managed
OpenCode server, whose internal title/summary generation reads small_model
from its config. With the override injected into OPENCODE_CONFIG_CONTENT at
managed-process launch, session title generation uses the user's explicit
model instead of falling back (or failing to resolve) — fixing sessions that
stayed untitled even with a Small Model configured.

Only an explicit override (smallModelUseDefault === false with a non-empty
smallModelOverride) is injected; "use default" leaves the config untouched
so OpenCode's own resolution chain stays authoritative. Malformed user config
is left unmodified. External OpenCode servers are unaffected (they are not
launched with this env).

Fixes #2497
2026-08-05 13:46:30 +03:00
Serhii Dziupin 7ff86a3bc7 fix(composer): restore Shift+Enter newline on iOS
CodeMirror defers Enter on iOS (and Chrome Android): the real keydown is
captured without running the keymaps and the keymaps then run against a
synthetic keydown that dispatchKey builds from the key name alone, with
no modifier keys. The composer's Shift+Enter thus arrived as a plain
Enter, and on devices where Enter sends (iPad Safari/PWA, where the
desktop layout applies) it submitted the message instead of inserting a
newline.

Record the real Enter keydown's shift state on the view's contentDOM and
restore it onto the deferred synthetic event before the caller's
onKeyDown policy runs, so Shift+Enter means newline again on every
runtime. Plain Enter behavior is untouched: on iOS it still follows the
same deferred path it used before this change.

Fixes #2558
2026-08-05 13:44:36 +03:00
Serhii Dziupin 81e8ee7c33 feat(sidebar): show compact timestamp in recent activity rows
The sidebar's recent activity list (SidebarActivitySections) rendered its
session rows without an inline timestamp on web/desktop — the compact
relative label only appeared in the hover tooltip and on touch runtimes.

Render the existing i18n-backed formatSessionCompactDateLabel inline in
the recent rows' metadata slot, alongside the goal/branch glyphs, for
web/desktop too. It keeps the same hover-fade as the other metadata, so
the hover-revealed row actions never overlap it, and the full date
stays available in the row tooltip.

No new strings: the label reuses common.relative.* keys.

Fixes #2560
2026-08-05 13:43:04 +03:00
Serhii Dziupin c1ba631964 feat(sessions): show a pending-question indicator on session rows
Adds a per-session pending-question badge to sidebar rows, driven by the
live directory-store question state through a dedicated per-session
subscription channel so unrelated streaming never re-renders rows.

Collapsed parent rows roll up pending questions of hidden descendants
from their owning directory stores without bootstrapping them. Question
state is cloned on session delete/archive so badges clear when sessions
disappear. Adds the questionChangeCallbacks sync performance counter,
i18n keys for all locales, and unit tests for the subscription channel
and scope selection.

Fixes #2634
2026-08-05 13:41:48 +03:00
Serhii Dziupin 9b7c032524 feat(ui): show the changed-files count badge on the Git rail surface
Replaces the plain activity dot on the context panel rail's Git button with
a numeric badge of the changed-files count from the git store status, so the
count is visible at a glance without opening the Git surface. Large counts
cap at 99+ to keep the pill within the 36px button. The badge is reflected
in the button's accessible label and the hover tooltip.

Fixes #2364
2026-08-05 13:37:46 +03:00
Serhii Dziupin 654e9cdb64 feat(chat): refocus composer after adding message to context
After the add-to-context (context pin) action completes successfully, move
focus back to the chat input so the user can keep typing immediately. Uses
the existing focusChatInput helper and the requestAnimationFrame refocus
pattern already used by the model/agent selectors.

Fixes #2447
2026-08-05 13:37:20 +03:00
Serhii Dziupin 6622d8889d fix(chat): keep sticky header gradient inside its padding
The gradient fade under the sticky user header was absolutely positioned at
top-full with h-4/sm:h-8, so it overlapped the first rows of the assistant
content below and obscured readable text (especially for headerless messages
with pt-0). Reserve the fade as bottom padding on the sticky container and
anchor the gradient to bottom-0, so it only covers the header's own padding
box and stays purely decorative with pointer-events-none.

Fixes #2524
2026-08-05 13:35:10 +03:00
Mayuresh K 34afcfbbeb Merge branch 'main' into reproduce/issue-1720
Signed-off-by: Mayuresh K <23300+mskadu@users.noreply.github.com>
2026-08-05 10:27:32 +01:00
Serhii Dziupin 498a029e51 fix(sync): settle completed turns and finished messages promptly
Two remaining stuck/incorrect busy-state edge cases from the post-#483
spinner audit (OPE-193):

- B1: when a turn ended but the session.idle SSE event was delayed or
  lost, the busy spinner kept showing until the next watchdog poll tick
  (~5s) and its escalation (~10s). An assistant message.updated that
  carries time.completed now triggers one immediate directory status
  poll (monotonic confirm, authoritative settle when the snapshot
  reports the session idle) — recovery drops to a single round-trip,
  with one in-flight fetch per directory and the watchdog poll as the
  backstop.

- C1: the streaming derivation marked the trailing assistant message as
  streaming while the session stayed busy even after the server stamped
  time.completed (whole response incl. tools finished) — the typing
  indicator and streaming part-update suspension lingered on finished
  content until the session settled or the next message started. A
  completed trailing message is now never marked streaming; both the
  full and incremental derivations complete the previous streaming
  message instead.

Refs OPE-193
2026-08-05 11:57:12 +03:00
Serhii Dziupin ff5814a731 fix(web): parse agent frontmatter as leniently as OpenCode
parseMdFile now matches gray-matter (used by OpenCode) for file shapes
OpenChamber previously failed to parse: frontmatter whose closing '---'
sits at end-of-file without a trailing newline, a UTF-8 BOM prefix, and
YAML with unquoted colons in scalar values (via the same sanitizer
OpenCode applies). OpenCode parses these files, so OpenChamber must
too: otherwise the whole file was treated as the prompt body and a
save rewrote the existing YAML block into the body, prepending a
duplicate frontmatter block.

Refs OPE-178
2026-08-05 11:48:31 +03:00
Serhii Dziupin 0116739111 fix(sync): route question/permission replies by the request's own session directory
Answering a question tool (or a permission prompt) could leave the session
permanently stuck on "asking question": resolveDirectoryForBlockingRequest
returned the containing child-store key, which only proves containment.
For a worktree session (or any session whose record is grouped under a
parent project store), the reply was addressed to the parent directory's
OpenCode instance, where the pending request does not exist - the server
answered QuestionNotFoundError, the local request was removed, and the
trailing question-tool part stayed running with no recovery until Stop.

Resolve the directory from the request's own session record (server-
confirmed ownership: session.directory, then project.worktree) before
falling back to the containing store key. When a reply/reject comes back
not-found, also enqueue the settled-running-tool tail materialization so
the tool part converges to the server's actual state instead of leaving
the UI stuck.

Refs OPE-236
2026-08-05 11:41:40 +03:00
Serhii Dziupin ddae6f2545 feat(server): validate OPENCHAMBER_OPENCODE_HOSTNAME bind hostname
The env var was already read and passed to the managed OpenCode server
spawn, but any non-empty string was accepted. Reject values that are
not a valid IP (IPv4/IPv6, brackets allowed) or DNS-style hostname with
a clear [config] error and fall back to the secure loopback default so
a typo can never silently bind a non-loopback address.

Refs OPE-231
2026-08-05 11:24:14 +03:00
Serhii Dziupin 41a2e3781d fix(cli): generate a UI password for bare --ui-password in daemon/serve mode
The grand tunnel restructuring removed the CLI's auto-generated UI
password, so `openchamber -d --ui-password` (no value) silently started
an unauthenticated server instead of creating a password as in 1.8.1.

Restore generation for an explicit --ui-password flag without a value:
the password is generated before either launch path, passed to the
daemon/foreground process via OPENCHAMBER_UI_PASSWORD, persisted in the
instance state file, and surfaced once in human/quiet/json output.

Refs OPE-216
2026-08-05 11:24:10 +03:00
Serhii Dziupin 1ad81a2c72 fix(chat): show context text before a pending question
A turn blocked on a question never reaches finish 'stop', so in sorted
render mode the model's text was classified as justification and the
inline-text deferral rule hid it inside the collapsible Activity group
until the turn completed — with a pending question that never happens,
leaving the context produced before the question invisible (OpenCode
shows it inline). Keep text inline for messages that contain a question
tool part: exclude them from justification classification and from the
sorted-mode text deferral.

Refs OPE-199
2026-08-05 10:33:54 +03:00
Bohdan Triapitsyn 34c221b07f fix(test): restore the issue-2039 suite by completing its session-actions mock
The suite mocks `session-actions` by listing its exports one by one, and had
fallen behind `unarchiveSession`/`unarchiveSessions`. `session-ui-store` imports
both, so the file threw on import and ran zero tests — the draft auto-accept and
canonical-worktree-directory guarantees it covers were unprotected, and the
report looked almost like silence rather than a failure.
2026-08-05 03:10:29 +03:00