* feat(markdown): support HTML superscript links
* Add HTML superscript citation links to the rich markdown editor
- Introduce marked-based tokenizer support for `<sup><a>` citation
links, with a shared editor context so key/link handlers can resolve
selection status and open an action bubble for citation atoms
(which markdown setLink/unsetLink can't edit).
- Extract clipboard-write logic shared by cut and cut-visual-line into
rich-markdown-clipboard-write.ts, and surface a cut-limit error when
clipboard readback fails.
- Fix MarkdownPreview same-file anchor scrolling to run before the
unknown-ownership guard so ambiguous folder-workspace ownership
still scrolls within the open document.
- Keep search's replace-disabled state derived from live matches
instead of a stale snapshot.
* Update lockfile to dedupe stale package versions after dependency resolu
- Removes duplicate/superseded resolutions (older @babel/* versions, fs-extra 11.3.4/11.3.5, plist 3.1.0, semver 7.7.4) left over from a prior install
- Aligns transitive deps to the single resolved versions already in use elsewhere in the tree
* feat(status-bar): notify upgraded users usage meters show % used
Show a one-time status-bar callout when upgraded profiles still use the
new percent-used default. Brand-new profiles and users who already chose
remaining stay quiet; dismissing or changing the setting is permanent.
* Add settings deep-link to expand Appearance's Window accordion for one-s
- Replaces the searchQuery-based redirect (fragile, flashed filter UI) with a
dedicated appearanceAccordionDeepLink store field that force-opens the
correct accordion and scrolls to the target row
- Rebuilds the status-bar change notice as a plain elevated card instead of
a Popover, since PopoverContent's glass/backdrop-filter defaults fought
the opaque callout styling and needed heavy overrides
- Simplifies the light/dark card CSS tokens accordingly
* Reposition status-bar usage-change notice via fixed-position portal
Portal the one-shot callout to document.body with fixed positioning
anchored via getBoundingClientRect, since the status-bar's overflow-hidden
flex ancestors clipped or mispositioned the previous in-tree absolute
layout.
* Refine status-bar usage notice styling and test coverage
- Replace hand-tuned light/dark card colors with existing design
tokens (--popover, --border) and the documented floating elevation,
so the callout stays in sync with the design system instead of
duplicating its own palette
- Add tests covering dismiss via X button, "Got it", and Escape
- Scope the foreground-process confirm assertion to the pane's ptyId
so an unrelated pane's delayed confirm can't cause a false failure
* fix(naming): lead workspace and tab names with the work-item identifier
Auto-generated workspace names, branch-rename display names, and tab
titles now lead with the referenced PR/MR/issue/ticket — e.g.
`PR 1033 - Review` instead of a paraphrase like "Review community pr
1094" that buried or dropped the number. Identifiers are the highest-
signal, most searchable token, so leading with them makes the sidebar
and tabs scannable and disambiguates same-verb work items.
- New shared `work-item-reference.ts` extracts the identifier from the
raw prompt: URLs are validated by path structure (owner/repo/pull/N,
GitLab's `/-/` marker) so GitHub Enterprise / self-hosted GitLab still
resolve while stray `/pull/<n>` paths (CDN, docs) do not; a ticket-
prefix denylist keeps `SHA-256` / `UTF-8` / `ISO-8601` from being read
as Jira/Linear keys.
- Reconciles the existing create-from-work-item naming (was action-first
`Review PR 1033`) with the first-work auto-rename onto one identifier-
first format via a shared `formatIdentifierFirst`, so the two paths
can't drift.
- Fixes a pre-existing tab-title bug where markdown punctuation was
stripped before URLs, splitting a GitLab `merge_requests` URL at its
underscore and leaking "requests" into the title.
* fix(naming): keep emphasis-wrapped URLs intact in generated names
The URL/markdown strip reorder left the tab-title URL strip anchored on
\b, which fails when a URL is wrapped in markdown emphasis (_...pull/5_)
and leaked URL fragments into the title. Drop the \b anchor and trim
trailing markdown emphasis (*_~) in the URL identifier parser, keeping
interior underscores (merge_requests) intact. Adds regression tests.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
Bound the PR/MR queued-comment selection cache to 1024 LRU contexts and make cache recency commit-safe so long renderer sessions cannot retain unbounded unsent selections.
The always-mounted observer that watches for successful agent completions
subscribed to the whole agentStatusByPaneKey map, which is re-spread to a new
object on every agent status ping (including high-frequency still-working
pings). That re-rendered the observer and re-ran the app-wide done-transition
scan on every ping. Subscribe to agentStatusEpoch instead — it bumps on exactly
the state transitions the observer detects — and read the map imperatively via
getState(), mirroring useActivityUnreadCount. Behavior is preserved; a Profiler
render-count test guards that non-epoch churn no longer re-renders.
Co-authored-by: Orca <help@stably.ai>
Add a persisted Appearance setting that switches provider usage labels between percent used and percent remaining while keeping meter fill consumption-based.
Cover desktop and web persistence, current providers, settings search, localized copy, and regression tests.
Co-authored-by: gatsby74 <166927047+gatsby74@users.noreply.github.com>
selectWorktreeAgentActivitySummary's shared cache keys on agentStatusEpoch, so
any epoch bump — an UNRELATED worktree's agent transition, or a freshness tick —
invalidated it and rebuilt every worktree's summary with fresh objects/Sets. Each
sidebar card subscribes to its own summary via useShallow, and the new nested
agentStatusPaneIdsByTabId ref defeated the shallow compare, re-rendering every
card with agents on every transition anywhere. Reuse the prior summary object
when structurally equal (booleans + Set-membership), mirroring reuseArrayIfEqual,
so only the transitioning card re-renders.
Co-authored-by: Orca <help@stably.ai>
* fix: sweep completed Pi agent row when its terminal tab is closed
A 'done' Pi agent entry lingers in agentStatusByPaneKey after its
terminal tab is closed: its paneKey's tabId no longer matches any open
tab, so dropAgentStatusByTabPrefix never sweeps it. The sidebar selector
getLiveEntriesByWorktree used an unconditional fallback
(tabIdToWorktreeId.get(parsed.tabId) ?? entry.worktreeId), added in
#4371 to surface early child-agent rows before their tab reaches the
renderer. That fallback re-attributed the orphaned 'done' entry to the
worktree, so buildWorktreeAgentRows rendered it as a stale live 'Done'
row forever (Codex/Claude tear down cleanly because their pane/tab
keying matches the closed tab).
Restrict the worktree-attribution fallback to non-'done' entries so
completed rows whose tab is gone are dropped, while still surfacing
active child rows whose tab has not yet reached the renderer.
Supersedes #5914 (adopted and re-verified).
Fixes#5913
Co-authored-by: Alberto Cuervo <Br1NKOL@users.noreply.github.com>
* fix: drop completed Pi orphan during tab close
* fix: skip retention suppressor for completed-orphan keys during tab close
A completed-orphan key's tab is already gone, so buildRetainedAgentsSyncSnapshot
never snapshots it and no live->gone transition ever fires to consume a planted
retention suppressor. Planting one leaked a permanent retentionSuppressedPaneKeys
entry per swept orphan. Plant suppressors only for prefix-matched keys.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Alberto Cuervo <Br1NKOL@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): let remote desktop viewers own the shared PTY width
A remote (relay/shared-control) desktop viewer resizes the host source PTY to
its own width, but was never registered as a width owner. So the host's own fit
cascade (window resize, split drag, tab reveal, "+"-new-tab re-render) freely
resized the viewed PTY back to the host-local width with no signal to the
viewer. The viewer kept its narrower grid while the host streamed wider
alt-screen frames -> cell-layout garble ("porridge") until a manual resize.
The mobile "presence lock" already solves this shape for phones by suppressing
the host's pty:resize while the phone drives. This does the same for remote
desktop viewers WITHOUT joining the mobile driver state machine (a viewer needs
only resize suppression, not input lock / phone-fit / driver banners), and it
routes every PTY geometry change through the existing enqueueLayout/applyLayout
serialization path rather than resizing the PTY ad hoc.
Design:
- Registry `remoteDesktopViewers: Map<ptyId, Map<subscriptionKey, viewport>>`,
keyed per SUBSCRIPTION so duplicate streams of one client cannot release each
other. isPtyResizeDrivenRemotely() = mobile driver OR any viewer present;
pty:resize (host fit cascade) bails on it. INPUT is never locked (shared
control: host and viewers can both type), unlike mobile.
- New internal layout target { kind: 'remote-desktop' } in applyLayout: clears
terminalFitOverrides like 'desktop', resizes only when dims changed, emits no
fit-override notifications, and does not call onExternalPtyResize (so a
viewer's width never pollutes desktop restore state). enqueueLayout remains
the sole serialized writer of PTY dimensions.
- Smallest client wins: applyRemoteDesktopLayout() sizes the PTY to the SMALLEST
attached viewer, so viewers with different screen sizes never overflow the
narrowest grid.
- No snapshot/replay race: a viewport that arrives while the initial scrollback
snapshot is being serialized is buffered and applied as ONE serialized resize
before serialization, so a mid-repaint alt screen is never baked into the
snapshot.
- Reclaim on detach: when the last viewer leaves, applyRemoteDesktopLayout
resizes the PTY back to the host's own width via enqueueLayout({kind:'desktop'}),
so the host reflows to full width without a manual window jiggle. The host
width is captured at the pty:resize suppression point (the host's own resize
attempts) — a source a remote viewport never pollutes.
- Mobile coexistence: a phone outranks a viewer; when the phone leaves, the
mobile-release paths call applyRemoteDesktopLayout so a surviving viewer keeps
the PTY, else the host reclaims.
Verified live (two local desktop instances): correct render on connect, no
garble on viewer-window resize (synchronous), and host reclaims full width on
disconnect. Unit tests cover the registry lifecycle, smallest-client-wins,
reclaim-via-layout, and mobile coexistence.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
* fix(terminal): address remote-desktop PTY width review findings
Resolves four CodeRabbit findings on the remote-desktop viewer width
ownership change, plus a round-trip race surfaced while fixing the leak:
- Leak: the one-shot terminal.updateViewport RPC has no disconnect hook,
so it must never create a width floor (nothing releases it, pinning the
host at a stale width). Add refreshRemoteDesktopViewer, which only
refreshes floors the client already owns via its stream subscription
(matched by clientId), mirroring the mobile updateMobileViewport
no-op-without-subscription invariant. The one-shot handler now refreshes
instead of registering.
- Round-trip replay: with the one-shot fallback now refresh-only, a resize
landing during the subscribe round-trip (connected, stream not yet
current) was dropped. The transport now replays the latest viewport over
the stream once it becomes current.
- Reclaim: keep the host reclaim target unless the reclaim resize actually
landed (result.ok), so a failed reclaim can retry against true host
geometry instead of a stale remote width.
- Snapshot drain: drain pendingRemoteDesktopViewport in the
sendRequestedSnapshot finally block; a viewer resize parked during a
SnapshotRequest buffering window was otherwise dropped until the next
resize.
- Key scope: scope the multiplex width-floor key by connectionId so two
connections reusing the same client-local streamId can't overwrite or
release each other's floor.
Tests: refresh-never-creates-floor, reclaim-target-retained-on-failed-resize,
SnapshotRequest parked-resize drain, connection-scoped key assertions, and a
round-trip replay race test (verified to fail without the flush).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(terminal): arbitrate active desktop viewport ownership
Co-authored-by: Orca <help@stably.ai>
* test(terminal): expect viewport claim capability
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): preserve input across ownership teardown
Co-authored-by: Orca <help@stably.ai>
* test(terminal): keep subscribe buffer coverage within lint limit
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: shady <shady2k@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Orca <help@stably.ai>
* fix(automations): deliver prompt to Hermes TUI via process-ready signal
Hermes's prompt_toolkit TUI never emits the DECSET 2004 bracketed-paste
handshake that the default draft-paste readiness waiter gates on, so the
automation prompt was silently dropped and the agent sat idle (terminal
opens, nothing entered). Add a 'process-ready' DraftPasteReadySignal that
arms the quiet-window on first PTY output and pastes once the TUI settles,
and assign it to the hermes agent config.
Adds a Hermes unit test covering the no-handshake path.
Design for the follow-up PR/issue review loop lives in
docs/design/pr-issue-review-loop.md (not committed; gitignored).
* fix(automations): skip broken process-name fallback for process-ready
CodeRabbit: the process-ready fallback consulted waitForAgentReady, which
compares the foreground process basename via isExpectedAgentProcess. Wrapped
interpreter launches surface as 'python3 .../hermes', so the check can never
confirm readiness and only drops the paste. process-ready readiness is the
PTY-quiet window (handled by waitForAgentDraftInputReady); the process-name
fallback is dead for this signal. Skip it and return false on timeout instead.
* test(automations): assert single paste for Hermes quiet-window path
Guard against a duplicate paste in the no-handshake Hermes path by
asserting sendRuntimePtyInputVerified is called exactly once. Addresses
the CodeRabbit nitpick on PR #7862.
* fix(automations): skip broken process-name fallback on pty-bound paste path
pasteDraftToAgentPtyWhenReady (quick-create/work-item route) still ran the
python3-vs-hermes process-name fallback that can never match for
process-ready agents, burning ~1s and dropping the paste. Mirror the
process-ready guard from pasteDraftWhenAgentReady and surface onTimeout.
Adds regression tests for both the quiet-window happy path and the
fallback-skip on the pty-bound path.
Co-authored-by: Orca <help@stably.ai>
* fix(automations): defer submit Enter until the Hermes TUI is interactive
Live-testing against Hermes v0.18.2 showed the fixed 50ms post-paste Enter
is swallowed: the node ui-tui takes 15s+ to boot, the paste fires ~1.5s in
(process-ready quiet window), and the cooked-mode line discipline turns the
early \r into \n, which the editor treats as newline-insert. The prompt
parked in the input box and the automation never executed.
For process-ready agents, defer the Enter until the TUI signals
interactivity (DECSET 2004 enable) or echoes the pasted content marker-free
(legacy prompt_toolkit), with a 120s best-effort cap. PTY input is FIFO, so
the deferred Enter always lands after the buffered paste text. The paste
echo of the content itself (raw or caret-notation markers adjacent) is
rejected so a cooked-mode echo can't release the Enter early.
Verified end-to-end in the dev app: automation prompt pasted, deferred
Enter released on tui-ready, Hermes submitted and ran the turn.
Co-authored-by: Orca <help@stably.ai>
* fix(automations): use Hermes native startup query
Co-authored-by: Orca <help@stably.ai>
* fix(automations): preserve quoted Hermes queries on Windows
* fix(agents): preserve invalid argument error message
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Brandon Bennett <brandonbennett@macbookair.myfiosgateway.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
* fix(files): keep SSH deletes on the remote path (#8135)
* fix(files): preserve explorer operation ownership
* fix(files): preserve runtime owner caller scope
* test(terminal): de-flake stale daemon launch identity ladder assertion
Co-authored-by: Orca <help@stably.ai>
* fix(files): fail closed on unresolved operation owner in delete flow
- Treat non-local operation owners (ssh, runtime, unresolved) as remote
for confirm/error copy, then fail closed when the route is null so an
unresolved owner never falls through to local filesystem authorization
- Fix test setup to preserve existing window properties when stubbing
global window, preventing unrelated globals from being clobbered
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* Fix mobile terminal query reply authority
* fix(terminal): harden mobile query reply handoffs
* fix(terminal): exclude passive mobile query responders
* fix(terminal): gate mobile query replies on host capability
Older hosts strip terminal.send's inputKind (zod drops unknown keys), so a
forwarded xterm reply would land as ordinary floor-taking shell input. Hosts
now advertise terminal.query-reply-input.v1 via status.get and mobile drops
replies unless the host advertises it (pre-fix behavior). Also documents the
bounded desktop-to-mobile handoff double-reply residual.
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): advance snapshot seq across recovery snapshots
The pending-overflow recovery loop trims buffered output against
recovery.seq while query replay and boundary strips kept using the
initial snapshot seq. Unreachable under today's control flow (no await
separates the initial-overflow consume from the loop), but the stale
seq would silently drop covered query replies if that ordering ever
changes. Track the seq that actually covered the buffered chunks.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(win): resume quoted cmd.exe startup commands via stdin, not /K
Resuming an AI Vault session into a cmd.exe tab on Windows failed with
"'...' is not recognized as an internal or external command" and never
ran the resume. The queued command for a cmd live shell is the
self-contained `cmd /d /s /c "cd /d ""cwd"" && claude ""--resume"" ""id"""`
form, which is correct when typed into cmd's interactive parser (its ""
doubling is cmd's convention). But the local PTY provider embedded it in
the `/K` launch argument, where node-pty's C-runtime argv escaping emits
backslash-escaped quotes (\") that cmd.exe cannot parse — the command
arrived as `\"cd /d \"\"cwd\"\" && ...` and was rejected wholesale.
Unlike PowerShell's -EncodedCommand, cmd.exe has no robust argv-quoting
path, so any startup command containing a double quote now falls back to
stdin delivery, where cmd's interactive parser handles the "" doubling
correctly (verified end-to-end against a real ConPTY via node-pty).
Quote-free commands keep the `/K` fast path.
* fix(win): preserve cmd resume command contracts
* fix(win): match copied resume commands to shell
---------
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
* feat(agent-status): show Claude subagent child rows and gate premature done
A Claude pane that spawned background subagents/teammates showed a green
done check the moment the lead's turn ended, even while a background
review loop was still running. Orca now tracks the pane's live children
from Claude hook events and:
- keeps the pane 'working' while at least one child is working (Stop is
gated; Claude wakes the lead when a child finishes, so the pane
resolves to done on the follow-up Stop with an empty roster)
- renders the children as indented child rows under the pane's sidebar
row (name/type + working/idle dot), reusing the existing lineage UI
Tracking is lifecycle-primary: SubagentStart/SubagentStop/TeammateIdle
(newly registered hooks) plus child-origin tool events (they carry
agent_id) own the roster. Stop's background_tasks is folded only where
unambiguous — verified live on Claude Code 2.1.207 that teammates report
status "running" while idle-alive and their task ids never match
lifecycle agent_ids, so the list cannot decide teammate working-ness.
Child-origin events no longer overwrite the lead's tool/prompt caches
(a live AskUserQuestion card survives child churn); a child's own
PermissionRequest records waitingAgentId so only that child's progress
or death clears the wait. The interrupted flag survives the gated
window, inferred interrupts sync the lead record and refuse while a
child works, and hydration reseeds the roster after a restart.
* fix(agent-status): drop identity icon on subagent child rows
The child's agentType carries its NAME (e.g. "pr-reviewer"), which is not
an iconable agent and rendered the unknown "?" glyph. Nesting under the
parent row already conveys identity.
* fix(agent-status): restore displaced lead state and reconcile phantom subagents
Four review findings from the adversarial pass on the subagent child-row
feature:
- Stash the lead state a child-induced wait displaces
(ClaudeLeadTurnState.stateBeforeWait) and restore it when the wait
clears, instead of inventing 'working' — a lead that had already
stopped left the pane spinning forever after the roster drained,
since the done-gate only ever downgrades done → working.
- Tag snapshot-seeded and background_tasks-recreated roster entries
(backgroundTasksAuthoritative) and demote them when a PRESENT
background_tasks list omits their id. A phantom child seeded before a
restart could otherwise gate the pane 'working' indefinitely in teams
sessions, whose task list is never empty. Live activity clears the
tag so lifecycle-tracked teammates keep their state.
- Match teammate ids with a hyphen-free suffix after `a<name>-` so
TeammateIdle for "lane" cannot idle "lane-hooks"'s rows or clear its
pending permission wait.
- Route turn-boundary events (Stop/StopFailure/UserPromptSubmit) that
carry a KNOWN child agent_id through the child-driven re-emit instead
of adopting them as lead state, and tie the prompt-cache new-turn
reset to lead-origin events so child refreshes can't blank the
prompt label.
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
* Fix tui-idle detection for Cursor Agent lanes with dismissed trust dialo
Cursor Agent's persistent TUI never emits an idle OSC title, so a
dismissed trust dialog stayed in scrollback and kept the wait blocked
even once Cursor reached its idle "→" prompt. Detect Cursor's active
prompt (busy or idle) to clear a stale trust hit, and detect its idle
state specifically (prompt present, no braille spinner) to satisfy
tui-idle, so busy lanes keep polling instead of returning a stale
blocked result.
* Add test coverage for Cursor Agent trust-dialog banner detection
Verify lastIndexOf picks the later ready banner over an earlier
"Cursor Agent" mention in the trust dialog body, for both the
idle-resolution and busy-detection cases.
* chore(i18n): sync localization catalog for aiVaultSessionLogOpen keys
Pre-existing gap from #8205 (ai-vault view log) that fails `pnpm lint`'s
localization-catalog check on origin/main; unrelated to the tui-idle fix but
committed here to unblock CI lint on this branch.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(source-control): keep push enabled for same-repo linked review with real upstream
Push/Force Push (and Pull/Sync/Publish) were wrongly disabled when a branch
had a genuine git upstream but an open linked PR whose push target had not
yet hydrated. `hasUsableHostedReviewPushTarget` hard-returned false for any
resolvable review link without a hydrated `pushTarget`, so
`resolveHostedReviewActionUpstreamStatus` synthesized `hasUpstream:false`
and the whole remote menu treated the branch as unpublished.
A same-repo review's head IS the checked-out branch, so a real upstream
already tracking that branch is that head and is safe to use before the
resolver hydrates a target. Treat that upstream as usable; keep blocking the
fork-head/cross-repo case (upstream tracks a differently-named head) and the
no-upstream case until the resolver proves the real target. Provider-agnostic
(GitHub PR / GitLab MR) and covers the SSH linked-review path.
Adds regression tests to source-control-hosted-review-push-target.test.ts and
a composed pipeline test in source-control-dropdown-items.test.ts.
* docs(source-control): trim verbose comments on linked-review push-target fix
Co-authored-by: Orca <help@stably.ai>
* refactor(source-control): use canonical gitRefTargetsBranchName for linked-review upstream match
Replace the hand-rolled upstreamTracksBranch leaf-split with the shared
gitRefTargetsBranchName primitive, which trims and rejects malformed
remote-qualified refs. The remote stays unknown until the resolver hydrates
the push target, so this remains a branch-leaf match; the strict remote+branch
check takes over once the target is known.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* Add read-only View Log tabs so AI Vault agent sessions open inside Orca'
- Adds a `readOnly` flag on OpenFile that hard-blocks edits, autosave, dirty
state, rename, and drafts, and persists/restores it safely across sessions
- Wires AI Vault's View Log/Open Log actions to open logs as permanent
read-only local tabs instead of shelling out to the OS, gated to
local, single-file, non-synthetic session paths
- Registers a dedicated `jsonl` Monaco language (JSON-style coloring without
whole-document JSON validation) and forces read-only tabs to render as raw
source, bypassing markdown/mermaid/csv/notebook viewers
* Add live-tail streaming for local AI Vault View Log tabs
- Extend the fs:readFile snapshot path with byte-stable file identity so a
read-only tab can resume appending exactly where the snapshot left off.
- Add a ranged local log tail reader plus IPC (read/start/stop watch) that
streams only newly appended bytes, detects truncation/rotation, and
cleans up watchers on tab close or renderer destruction.
- Add a renderer-side UTF-8/line-boundary decoder and useLocalLogTail hook
that appends completed lines into the existing Monaco model, falling
back to a full reload on reset/rotation, keeping snapshot behavior
unchanged when liveTail is not opted in.
- Thread the new `liveTail` flag through OpenFile/PersistedOpenFile,
workspace session persistence/restore, and the AI Vault "View Log" open
path so live tail survives restarts and stays read-only-safe.
* Remove stale planning brief for the live-tail feature
The AI-VAULT-VIEW-LOG-LIVE-TAIL.md pick-up brief is no longer needed now that the live-tail streaming work has landed (c7fdef7b1).
* fix(terminal): send CSI-u Shift+Enter to kitty TUIs (droid) on Windows (#7620)
On Windows, Shift+Enter was always sent as the Alt+Enter byte ESC+CR (added in
#2418 for Codex, which reads win32-input-mode and ignores CSI-u). droid speaks
the kitty keyboard protocol, parses CSI-u directly, and treats ESC+CR as a plain
Enter — so Shift+Enter SUBMITTED the message instead of inserting a newline.
droid works in other terminals (Windows Terminal, Warp) because those honor
win32-input-mode / kitty; Orca (xterm.js) withholds kitty from local Windows
ConPTY panes and emits neither.
Make the Windows Shift+Enter byte pane-aware: latch whether a pane's program
advertised the kitty keyboard protocol (query CSI ? u, push CSI > .. u, or set
CSI = .. u) and send CSI-u (\x1b[13;2u) to those panes, keeping the
Codex-compatible ESC+CR for win32-input-mode-only TUIs. Non-Windows is unchanged
(always CSI-u).
Verified end-to-end against the real droid and Codex CLIs through the actual
production functions: droid now newlines, Codex still newlines.
* fix(terminal): route Windows Shift+Enter safely for Droid
---------
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>