* plugins: orca plugin shot --editor, and plugin open --editor opens the file's folder
plugin open --editor now opens the file in the workspace that holds it,
matching its real path too (/tmp vs /private/tmp), and otherwise adds the
file's folder as a folder workspace instead of failing with no_workspace.
plugin shot <dir|key> --editor <file> [--theme] [--width --height]
[--act] loads the editor afresh in a hidden tab, waits for it to get its
document and ask for it (a new editor.subscribed frame event), captures
the editor's guest with the panel shot's capture path, and sends --act
clicks, drags and keys as real input via sendInputEvent. It exits 1 with
the frame's errors when the editor throws, is blocked by its content
policy, or never asks for its document within 5s; theme and size are put
back. New RPC method plugins.shotEditor so older hosts fail cleanly.
* plugins: shot --editor never changes the author's file
With --act the shot copies the file into a temporary folder, shoots the
copy in a temporary folder workspace, then closes its tab and removes the
workspace and folder; --in-place acts on the file itself. Without --act
it opens the real file, leaves its buffer alone and closes the tab if the
shot opened it. Afterwards Orca shows the workspace it showed before.
Regenerates the skill's CLI reference.
* plugins: fix editor shot typecheck (refusal next step, RPC test mock)
* plugins: editor shot targets its own guest, flushes before reload, scopes cleanup per capture
- Main captures the exact guest WebContents the shot's tab mounted, not the first with its URL.
- An open editor's pending edits are flushed before the shot reloads it; the shot fails if not.
- --act --in-place keeps the acted tab and its unsaved edits.
- end-capture undoes only its own capture (per-capture id), so overlapping shots don't clash.
- The file's workspace lookup includes local folder workspaces.
* plugins: shot --status-bar/--window, key acts focus the editor guest, panel shot selects a workspace
- Editor --act keys first give the guest's <webview> DOM focus and refocus its target, so a canvas
that keeps focus no longer drops later keys; never webContents.focus(), which focuses the window.
- orca plugin shot <dir|key> --status-bar | --window [--theme]: captures Orca's status bar or the
whole hidden window (plugins.shotChrome) with the existing capture helpers.
- With no workspace showing, a panel shot selects this machine's most recent workspace in the
hidden window instead of failing.
* plugins: a shot's end restores only its own capture overrides
* plugins: regenerate the testing runtime with the editor subscribed signal
* plugins: editor capture refusals go through one CLI-message helper
* fix(plugins): give the chrome shot refusal a next step
recipes.md gains "A rich editor with a library" beside the hooks and status
bar recipes: an isolated-frame custom editor that vendors its library behind
an import map, syncs through orca.document.session with a merge by id, and
tests that sync with mockEditorHost. host-api.md lists every --orca-* frame
variable and what it paints, generated from the frame token allowlist.
* feat(plugins): check custom editors statically and add mockEditorHost
orca plugin check now follows each custom editor entry (local script src,
inline module scripts, relative imports), transpiled and resolved as the
frame loader does, and fails at file:line:column on syntax errors, missing
files, refused imports, string eval/Function and files over their size
cap, with no running Orca. The check ships in the test hooks bundle.
orca/testing gains mockEditorHost: it runs the frame shell's real
window.orca (document API and session) over an in-memory text or bytes
file, with edits, replace, conflictNext, flush, setTheme and dispose.
Typings, docs and the skill guide cover both.
* feat(plugins): warn on editor eval and check editors in dev watch
editor_eval comes from a lexical scan that can misread rare code, so it is
now a warning that does not fail orca plugin check. dev watch --check runs
the editor check beside the panel check and reports it under editors.
* fix(plugins): check editors by the frame protocol's rules; mock blocks lost saves
The static editor check now reads files through the loader's contained
reader (symlinks out of the plugin are refused), runs only JavaScript
script types, honors the page's import map for native modules, classifies
files by the protocol's content types (JSON imports, extensionless HTML
entries), decodes escaped script URLs, and uses the protocol's caps: 10 MiB
entry, 1 MiB transpiled module, 16 MiB checked dependency.
mockEditorHost refuses an oversized edit before reading it and then rejects
flush() until a later edit lands, as Orca blocks the save.
* test(plugins): type mockEditorHost's frame API instead of reflecting on it
* fix(plugins): keep main's case-spelling check and hook typings through the rebase
The rebase onto main resolved plugin-artifact-validation.ts and
orca-testing.d.ts to B4's side; this puts back main's exact-path-spelling
check and hooks typings beside B4's contained reader and mockEditorHost
types, and regenerates the testing bundle.
* plugins: agent.tool.before and agent.tool.after hooks; Claude Code PreToolUse blocks
The hook server runs plugins' tool hooks after recording status. Claude Code's
POSIX hook script now asks for an answer on PreToolUse (X-Orca-Hook-Decision),
waits at most 8 s (Orca answers within 6 s) and prints the decision: a deny
with the plugin's reason, or updatedInput without approving it. Timeouts,
errors and an unreachable Orca print {}, so the call proceeds. Other events
still print {} before posting. Every other agent, Windows scripts and SSH/WSL
relays only observe or are not hooked yet (per-agent table in the
agent-hooks README).
Co-Authored-By: Claude <noreply@anthropic.com>
* plugins: verify PreToolUse answers against the real Claude CLI; 204 at once when no tool hook listens
A real-CLI test (ORCA_REAL_CLAUDE_CLI_TEST=1) shows Claude Code 2.1.316 runs
updatedInput sent without permissionDecision, still asks for approval of the
rewritten call, and refuses a deny. With no plugin listing a tool hook, the
server answers 204 before any await and runs no chain.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(claude): type the real-CLI PreToolUse answer
* plugins: harden agent tool hooks (partial replies, fenced posts, OpenClaude, Amp/Copilot/Cursor)
- Drop a failed curl's partial body and print only a complete PreToolUse decision.
- Run tool plugins only for posts status ingestion accepted; name the owner's terminal.
- Enable PreToolUse decision mode only for Claude Code, not CLIs reusing its script.
- Decode Amp tool.call/tool.result, Copilot envelope/implied events and Cursor shell/MCP calls.
Co-Authored-By: Claude <noreply@anthropic.com>
* plugins: regenerate the testing bundle on the rebased tool hooks
Generated file only (lint-staged's oxlint refuses a commit with no lintable files).
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* plugins: agent.launch.before and agent.prompt.before hooks; orca plugin dev hook
agent.launch.before runs at the PTY spawn every agent terminal goes through
(window tabs, runtime/CLI agent launch, automations, orchestration), before
dispatch to its host: change the command line, add env (Orca's own ORCA_*
variables and the agent's environment stay hidden), or deny. Restores and
resumes are not hooked. agent.prompt.before runs in sendTerminalAgentPrompt
(launch prompts, orchestration dispatch, terminal send to an agent) with the
agent named: rewrite or deny. The terminal hooks move into one runtime layer.
orca plugin dev hook runs a hook through one dev plugin and prints the decision.
Co-Authored-By: Claude <noreply@anthropic.com>
* plugins: recheck rewritten launch commands, forward hook env to WSL, keep prompt delivery outside the hook order
- A command agent.launch.before rewrote passes the AI Vault resume check again
on both spawn paths.
- Variables a hook adds are named in WSLENV on Windows so WSL agents get them.
- agent.prompt.before holds the terminal's order only while it decides; a
delivery waiting on a permission answer no longer blocks later input.
Co-Authored-By: Claude <noreply@anthropic.com>
* plugins: plugin dev hook honours onError: deny
Co-Authored-By: Claude <noreply@anthropic.com>
* plugins: regenerate the testing bundle and skill guides on the rebased hooks
Generated files only (lint-staged's oxlint refuses a commit with no lintable files).
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
On Linux the probe launcher appends --no-sandbox, so argv[length - 2] is the --user-data-dir flag rather than the config path. The page then wrote result.json under a directory named after that flag, threw, never exited, and the launcher timed it out at 30s on every Linux run since #37300. The sibling Electron probes already pass the result path in config.json; this one now does too.
* plugins: contributes.hooks runtime with terminal.send and workspace.create hooks
Workers register orca.hooks.on(hook, (event, next) => ...). Orca runs listed
plugins in order before it acts: next(event) continues with allowed fields
changed, { deny } stops, nothing passes. A plugin past its 2 s deadline, or
one that throws, crashes or is disabled, counts as passing and is logged with
plugin, hook and duration. First hooks: terminal.send (runtime sendTerminal),
workspace.create.before (IPC and runtime creates, folders included) and
observe-only workspace.create.after. orca/testing mountWorker gains
hooks.fire. Needs main + trusted-worker and engines.orca >=1.4.248.
Co-Authored-By: Claude <noreply@anthropic.com>
* plugins: keep hook plugins' workers started; bound start-up separately from the hook deadline
Workers of enabled plugins that list hooks start as soon as they are approved
and are never idle-reaped; crash restarts follow the manager's policy. A hook
waits up to the worker ready timeout for start-up, then gives the handler its
full 2 s, so a cold worker no longer lets the first hooked action through.
Co-Authored-By: Claude <noreply@anthropic.com>
* plugins: give hook workers their own soft cap instead of the active-worker cap
Always-running hook workers no longer hold slots of the shared 5-worker cap, so
many hook plugins cannot starve the rest. They have a soft cap of 16; past it a
hook worker waits for another to stop, logged once.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(plugins): type the hook warmer's ensureWorker mock
* plugins: address hook review: ordering, human input, overflow, revalidation, folder creates
- Hook workers past the soft cap start as overflow (logged once) instead of
waiting, so reconciliation, disable and uninstall never hang.
- Writes to one terminal stay in order while terminal.send decides, so a later
Enter cannot submit the previous line.
- terminal.send runs only for programmatic sends (main, plugins, the CLI);
desktop, mobile and legacy client input is a person's typing.
- Text a hook rewrote passes the AI Vault resume check again.
- Project-group folder workspace creates (RPC, agent launch, window) run the
workspace.create hooks through one boundary.
- A decision from a plugin disabled or changed while it ran is discarded.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(plugins): drive hook start-up timing with fake-timer deferreds, no real sleeps
Co-Authored-By: Claude <noreply@anthropic.com>
* plugins: only the hook decision holds a terminal's write order
A delivery that waits (a permission prompt, the launch composer) no longer
blocks later writes, and a person's input, interrupts and query replies never
wait behind a pending decision.
Co-Authored-By: Claude <noreply@anthropic.com>
* plugins: hook DX: onError deny, denied_by_plugin, a Hooks reference, --template hook
- contributes.hooks[].onError: "deny" makes a throw, timeout, crash or bad
answer refuse the action, naming the plugin; the default still passes.
orca/testing mountWorker honours it, and mockHost answers fire-and-forget
methods (result { ok: true }) with success by default.
- A plugin denial reaches RPC clients as denied_by_plugin with the plugin and
reason, and the CLI no longer suggests a retry for it.
- The skill's host-api reference gains a generated Hooks section (event, what
next may change, stop result); the recipe passes sends through unchanged and
warns against awaiting other calls before deciding.
- orca plugin create --template hook: a worker-only terminal.send guard that
fails closed, with a test.
Co-Authored-By: Claude <noreply@anthropic.com>
* plugins: regenerate hook docs on main; bind the plugin runtime and its hook runner in one call
Keeps main-process-plugins.ts under 300 lines now that status bar items landed.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
The probe waited for an exact count of refusal lines, but markup-only fonts and media load
only on layout, which a hidden Linux CI window may skip, so it timed out at 30s. The page
now requests each kind from script, settles each on its own outcome, and logs a done line
the main process waits on.
* Resume direct work item launches after setup confirmation
* Preserve setup review and settle cancelled work item preparation
* Fix setup test types and synchronize localization artifacts
* Keep accepted setup launches owned through completion
* fix: declare accepted create dialog result
* fix: report direct launch preparation failures
* Reuse workspace setup preparation and section
* Use compact dialog description for setup help
* Construct omitted setup decision fixture through serialization
* Match optional signal in background setup trust test
* plugins: contributes.statusBarItems with worker updates (statusBar.update)
A plugin declares status bar items { id, text?, icon?, tone?, tooltip?,
command? } (declarative profile, engines >=1.4.247); pressing one runs
its own command. A trusted worker changes them with the new worker-only
host method statusBar.update; main keeps the session's changes and
pushes snapshots to desktop windows over plugins:statusBarChanged.
Regenerates schemas, references, typings and the authoring skill.
* plugins: statusBarItems ship in 1.4.248, beside contributes.hooks
* plugins: clear a plugin's status bar changes when it is disabled, removed or updated
* plugins: list status bar items in the plugin page's Adds section
* plugins: count statusBarItems' since in the host's newest readable version
* i18n: add the plugin page's status bar row label to en.json
* plugins: status bar items respect workspace commands, dev reloads and catalog summaries
- An item whose command needs a workspace is disabled with no open workspace.
- A dev code edit (new devRevision) drops the old worker's status bar changes.
- Catalog summaries list status bar items, and marketplace details show them.
* plugins: authoring skill lists status bar items as buildable; approval lists them
The skill no longer calls status bar items reserved and gains a row for them. The
dev-load/inspect approval already lists them via the contribution summary; a test pins
that and the fingerprint binding.
* chore(plugins): regenerate the testing bundle after rebasing on main
* fix(plugins): skip nested dev packages and count blocked frame requests
Dev folders and their watcher now skip .git, .orca and node_modules at any
depth, so a nested build/node_modules no longer trips the 2,000-file limit
or re-reviews the plugin; installs still hash everything but the root .git.
Requests a plugin frame makes off its own files (session-cancelled or
refused by its content policy) are counted per kind and origin and logged
every 5 s and on teardown, outside the 20-lines-per-10-s console budget:
[frame] blocked font-src https://unpkg.com (x48): plugin frames have no network
* fix(plugins): read current CSP refusal wording and bound blocked-request logging
- Electron 43 prints "Loading the font '…' violates …" (and Connecting to / Framing /
Fetching content from); read it alongside the older "Refused to …" wording, name the
unset directive from the fallback note, and treat fetch's repeat as an echo. An Electron
test captures the bundled runtime's real messages.
- Keep at most 32 kind-and-origin counts and log at most 8 lines per flush, folding the
rest into one overflow count line.
* feat(plugins): add orca.document.session() for custom editors
One call replaces the document sync every editor rebuilds: decode/encode
(plain text by default), optional merge, debounce. One edit in flight with
later changes coalesced; the baseline moves only on accept; a refused edit
keeps the draft and rejects flush(); own accepted edits never reach
onReplace; a conflict merges once against the last accepted value and
resubmits, or keeps the draft without merge. The session answers
editor.flush itself and resolves after hello and the first document.
The --template editor now uses it. Docs and typings state the session and
the undo model (the editor owns undo).
* fix(plugins): let the outside document win a session conflict without merge
Agents write files while people edit them, so keeping the draft lost the
agent's write on the next keystroke. Without merge the session now drops the
draft, fires onReplace with the outside value and rejects flush() with
conflict; editors that want both sides pass merge.
* fix(plugins): serialize session edits through saves and settle before answering
- One edit in flight at all times, saves included; editor.flush is answered
only once every change is accepted or refused with nothing newer to send.
- The editor host commits a held edit when a save starts and admits edits
during the save at once, so waiting fits the 1 s deadline.
- A change generation replaces identity checks, and sent bytes are
snapshotted, so a reused buffer is never mistaken for an accepted edit.
- A waiting flush keeps its conflict rejection when the outside document
wins, and nothing merges or sends after dispose.
* test(plugins): read session test edits through typed helpers
* fix(plugins): drain session flushes, reject dropped drafts, snapshot the baseline
- flush() keeps going until no change made during it is left unsent.
- A waiting flush rejects with conflict whenever the outside document
replaced a draft, newer ones included.
- The accepted baseline is decoded from the sent snapshot, so a reused value
buffer cannot change merge's base.
* test(plugins): name the session test option and message types
* feat(plugins): let a custom editor require isolated frames
customEditors[].frame: "isolated" | "any" (default any, since 1.4.248).
An isolated-only editor never runs in the srcdoc fallback: main serves its
fallback shell without the entry, and when this window has no isolated
guest (browser clients, or desktop with isolated frames off) the file opens
in Source with "This editor needs the Orca desktop app". Picking the
editor shows the same notice with an Open Source button.
* i18n: add the needs-desktop editor notice strings
* plugins: refresh frame isolation when its setting changes; browser clients open Source
- The learned isolated-frames answer is tied to the setting it was learned under, so turning
isolated frames back on lets isolated-only editors open by default again.
- A browser client's plugin bridge has no list, so the plugin store settles on no plugins
instead of erroring and retrying; files open in Source.
* Preserve repositoryless GitHub Project items
* Keep only draft and restricted Project items without a repository
Key the repository-scope exemption on item kind on desktop and phone so
an older host's content-less Issue/PR rows stay hidden as before. Draft
titles, cards and bars stay non-interactive with no row browser icon;
the toolbar's Open view in GitHub is the Project-level action. Phone
labels restricted items "Restricted" to match desktop. Normalization
assertions move to a main-process test so renderer tests no longer
import main code.
* Cover draft Status normalization and phone restricted-item copy
Restore the check that a draft's single-select Status survives
normalization, and render the phone Project list and detail drawer with
a draft and a restricted item using the real status labels.
* Keep recovered conversations out of history warnings
* Name the agent when a History conversation is too large to load
A conversation whose only records exceed the transcript record limit now reports its own coded reason naming the agent and suggesting resuming it in that agent, instead of the generic session-metadata message. Adds a rendered banner test and a non-user Codex case.
* Build History banner test values without type assertions
* Name the conversation's writer in the too-large History reason
Labels the reason with the parsed agent so an OMP transcript found under Pi's folder names OMP, ships the new key in the English runtime catalog, and renders the reason in the phone's History panel test.
* Cover the phone History notice for readable results and Spanish
Test-only: a ready result without issues shows no skipped notice, and the too-large case keeps its English reason after switching the phone to Spanish while the count is translated.
* fix(plugins): flush custom editor frames before a tab closes or reloads
Closing a custom-editor tab or reloading its frame tore the frame down
without asking for edits still in its debounce window. Close paths now
send editor.flush (bounded by the existing 1 s deadline) and run the
dirty check once it settles; a reload keeps the old document mounted
until its flush settles. Read-only, windowless and dead frames settle
at once.
* fix(plugins): flush custom editor frames before a view switch, window close and removal list
Switching the view toggle away from a custom editor unmounted its frame
without a flush; it now switches once the frame's flush settles. Window
close/quit flushes mounted frames before its dirty check, and the
worktree-removal unsaved list asks frames to flush so it lists their
edits. Renames closeAfterEditorFramesFlush to runAfterEditorFramesFlush.
* fix(plugins): re-resolve renamed tabs, claim quit before flush, close unframed tabs at once
When output resumes after dropped bytes or after Orca's own CAN-led reset,
xterm's parser no longer matches the app stream, so the tail of a split
escape (e.g. the "2C" of ESC[2C) printed as text on Claude Code's prompt row.
- One admission check for live, restore-drain and abandon-drain writes skips the
continuation main labels with gapEscapeState, or the escape our reset aborted,
only when seq contiguity or our own grounding proves it.
- Grounding writes record the escape they abort; replay/PTY change/exit reset state.
- Pre-handler head truncation drops the now-wrong gap label.
* fix(tasks): publish confirmed dialog assignee changes to the list
Record successful per-login changes in the existing assignee snapshot and
patch the Tasks cache. Qualify snapshot writers, composition and quiet
release by canonical repository so fork and host twins stay isolated.
Co-authored-by: Max Hsu <maxmilian@gmail.com>
* test(tasks): bound confirmed assignee publication work
* Add explicit task refresh through registered details
* Refresh registered tasks with finite request ownership
* Preserve addressed task display and ordinary edit boundaries
* Derive task membership from owned rows and batch cache updates
* Preserve task list intent across account changes and query resets
* Preserve GitHub task content through final delivery
* Preserve accepted task content across cold query changes
* Batch finite task delivery publications and index ordered list folds
* Capture immutable Tasks publication inputs and translate Refresh feedback
* Preserve live scalar settlement beside captured publication inputs
* test: make task refresh contracts deterministic for static checks
* test: derive lifetime token annotations from production origins
* test: initialize current repo catalog defaults for refresh fixtures
* Fix refresh import dependencies for static checks
* Fix GitHub task refresh adapters and verification inputs
* Exercise omitted mutation observation in overlap contract
* fix(tasks): refresh live list bases and align delivery contracts
* test(tasks): retain actual pagination setter inputs
* fix(tasks): express page array mutability with checked overloads
* fix(tasks): preserve complete models through refresh stages
* fix(tasks): align origin and query row input contracts
* test: declare portable task refresh mock contracts
* test: allow zero-argument task refresh mutation calls
* Keep GitHub task copies fresh after edits and refresh
* Keep task rows visible and simplify the shared task record
Rows are never filtered for a stale connection or account binding; only a
current record for the row's own repo overlays its facts, derived at read
from repoId/repoHostId. Records cover issues only, conflicts compare only
state, title, label names and assignee logins, and truncated Project lists
never count as conflicts. Project tables stay raw in the store and are
projected where they are read.
Ordinary local dialog opens keep cached, lenient reads; only Refresh asks
for the fresh, strict read. Refresh waits on the record's own pending edits
instead of a second write registry, reports a failed user Refresh once
through the existing toast, and explains in its tooltip when the server
needs an update. Background verification never shows a failure.
A successful dialog assign keeps the selection until a newer reading lands,
main's soft-hide for Tasks-row edits is unchanged, and pins/eviction are
gone because only user edits and Refreshes create records.
* Let saved descriptions win over Refresh and roll back disposed edits
Refresh is now the only fresh, strict read: it writes the same details
entry ordinary loads use, and later reopens and post-edit refetches go
back to the cached, lenient path. An issue's saved description supersedes
any read already in flight, so Refresh rereads after the save instead of
restoring the older body. A failed Refresh with nothing loaded yet leaves
only the body's existing load error.
A record disposed while its edit is in flight still settles the copies its
edit patched, so a failed edit rolls back in Tasks and the dialog as before.
A record whose own connection fence went stale is dropped when next
looked up. Rows without a record pass through acceptSearch unchanged.
* Settle disposed edits through their owner and connect Project edits
A late edit whose record was disposed mid-flight now finishes through the
same base/pending reducer: copies receive only that operation's own change
(one label or login added or removed, a state set), a replacement record
reconciles with its own item read, and settle never returns null so dialog
and Tasks rollback always run. Untracked edits publish the same semantic
change at begin and undo it on failure. Dialog callers stop re-patching
Tasks rows; an unsupported server's live record keeps new edits joined
until its last one settles.
Refresh clicked during the first details load waits for that load, then
runs the strict read; a failed click always shows its one toast and never
installs an inline load error.
Project-row issue edits now publish to Tasks copies with no prior record,
seeded from a complete Tasks row for the matched repo binding, or
established by an item read after the edit lands.
* Give a task record its base only from an accepted provider reading
Starting an edit or a Refresh no longer turns the caller's cached row into
the record's full value. Until an owned canonical read is accepted, a record
holds only its pending changes and the coalesced successful ones, and every
copy (Tasks rows, pickers, the board) shows its own facts plus only those
changes. A successful edit applies just its change to the stored copies and
keeps it until one coalesced read through the existing read lane
establishes the base; a failed edit with nothing else owed retires the
owner, and a failed first Refresh leaves nothing behind. Search deliveries
and board fetches wake a read still owed.
Edits are now semantic change values (set title or state, add or remove a
named label or login) instead of closures, so they coalesce and apply to any
copy. Project edits keep their producer but no longer seed from a cached
row, and the separate establishing read is gone. A Project save with no
title, label or assignee change (a body-only save) skips the record
entirely.
* Keep pending task edits out of stored copies and re-admit queued reads
Accepted records now follow the rule records without a reading already
followed: stored Tasks copies hold only accepted readings and confirmed
changes, and pending changes reach users only through read-time projection.
A search during a pending edit stores the accepted facts, never the edit; a
failed or disposed edit just drops its token instead of computing an undo
over current copies, so a newer reading can't be overwritten by an old
edit's rollback. The cross-copy undo helper is removed.
Without a tracked owner (no binding, or a server without the item route),
other copies change only when an edit succeeds, and a board edit on a
server known to lack the item route stays board-only. Dialog state and
label edits roll back to what the dialog showed.
A canonical read queued behind another one re-enters admission after each
wait, so a Refresh clicked after a discarded one still reads, and a read
whose binding or server support went away meanwhile ends without leaving
an owner.
* Store only readings, keep task data across reconnects, park failed reads
Stored Tasks copies (the fresh-cache fetch return, details settle) hold only
accepted readings and confirmed edits; Cmd-J applies its open-only filter to
what it shows, pending edits included. Records, details entries and
optimistic comments are keyed by account binding and issue; a connection
change re-captures the binding and fences each read and send without
deleting what the dialog shows. A server's missing route marks only the
connection that answered. An unusable canonical read keeps what is known
and waits for the next provider delivery, which retries one owed read per
binding, the longest-waiting first. The dialog's label chips follow the
displayed item, and the duplicate target fence and write-only details
generation are removed.
* Park only reads that were owed, so a failed Refresh never delays a later one
A failed Refresh on a record with nothing owed no longer marks it as a parked
retry; a later obligation on that issue reads on the next delivery instead of
waiting behind every parked record. The refresh hook stops returning its
unused target.
* Keep the ordinary-open details case after main removed its suite
Ports the one case that pins an ordinary open with an issue repository to the
cached, lenient read (a failed timeline read still returns details) into the
main details suite.
* Keep pull-request lookups off the task records and repair CI tests
Pull requests never have a task record, so their lookups no longer load the
store or report a search reading; terminal PR-link confirmation reads as on
main. Tests follow the explicit canonical details flag, the current dialog edit
signature (records hold a dialog close over a lagging search), confirmed-only
untracked publication, and a fixed board timestamp.
* Keep main's close hold where no task record can own it, and split the record cache
A dialog close or reopen that no task record can own (no binding, or a server
without the item route) keeps main's confirmed-state hold, so a lagging search
cannot show the issue open again; a record that takes over retires it. Tracked
edits still go through the record only.
The record cache moves, unchanged, into model, owners and reads layers under
the Project-facing cache, so no module needs a max-lines exception.
* Hold an untracked dialog close only after it is confirmed
A dialog close or reopen no record can own changes no stored copy while the
request runs, so a refresh during a failed request leaves every copy as it was
and nothing needs undoing. At success the edit is admitted again: a record that
can now own it (after a reconnect or upgrade) settles it, otherwise main's
confirmed-state hold is asserted and the change is published once.
* Name the task binding incarnation token type
The binding incarnation, compared by reference, gets its own named type instead
of the broad object type; no behavior change.
* Import Cmd-J task search results from one statement
* Type changed code without assertions and keep the assignee callback stable
Tests build typed fixtures and hoisted mocks instead of casting; the landing
refresh takes only the model parts it reads. Lookups and the comments parse
annotate their values instead of asserting them. The edit section's empty
assignee list is one shared value, so its toggle callback keeps its identity.
* Stamp the opened row onto main's details item before it reaches typed record reads
* Pin the parked-read and board-read budgets at 10 and 100 records
* Size the dialog's Refresh task button like Copy link and Open on GitHub
---------
Co-authored-by: Max Hsu <maxmilian@gmail.com>
* fix(plugins): keep custom editor document in step with accepted edits
- orca.document.get() returns the content of an accepted applyEdit at the
same version, without firing onDidChange; a newer document wins.
- onDidChange skips its replay for a subscription disposed before it runs.
- The --template editor keeps unaccepted typing when the file changes
outside, and shows a refused edit in the frame instead of only logging.
- Extract the srcdoc shell mount into a shared test fixture.
* fix(plugins): acknowledge custom editor edits only once committed
- The host answers a superseded held edit with the answer of the edit that
replaced it, and keeps a held edit when an oversized one follows (the
save stays blocked), so success always means the buffer has it.
- The frame shell copies accepted bytes so a reused buffer cannot change
what get() returns.
- The editor template settles dirty and its notice only on the newest
input's answer.
* test(plugins): annotate the frame shell mount fixture's types
* Keep deleted-workspace chat Resume in History
* Address review: keep notification bounce, announce History toast, tidy refusal outcome
* Fix mobile type narrowing in History open failure
* Re-measure mobile web bundle sweep after History notice import removal
* fix(sidebar): keep the host pill legible on the selected workspace card
* fix(sidebar): keep nested lineage cards out of the selected host pill rule
* fix(sidebar): leave the host pill label on its theme-validated color
* fix(sidebar): derive the selected host pill border from the validated sidebar foreground
* fix(ghostty): import the primary font-family from a repeated fallback list
* fix(ghostty): report dropped font-family fallbacks on import
* fix(ghostty): append font-family across config files like Ghostty
* Keep chat pickers usable while options recover
* Show only the current process's reported permission and effort after a restart
When a native chat's agent restarted, the first frame at the new process could still show the old process's reported permission (for example "Full access" on an Own chat) as pickable, then flip to unknown a moment later. A reported value now counts only while the options answer that carried it belongs to the current process, on desktop and phone.
Also updates the restart tests: a pick made while the new process's options read is pending or failing goes to the new process on both desktop and phone.
* fix(cli-runtime): preserve advisory inspection results on close failure
* fix(appimage): keep close errors from overriding verified inspection results
The AppImage identity and stable launcher readers closed their read-only
handle in an unguarded finally, so a close error replaced the verified
result and broke CLI status, startup crash capture and launcher upgrades.
Attempt the close once and discard its error, as the CLI runtime check does.
Also load the real closeSync in the node:fs mock factory instead of inside
the test body.
* test(cli-runtime): drop type assertions from close-failure fixtures
* Clarify Hand Off when detected agents share the source usage limit
* Base the Hand Off family explanation on offered agents
Ignore agent ids this build does not know, count chats offered without
their CLI, and say the source-family agents are available rather than
detected. Keep Agent terminology consistent with each catalog.
* test: feed the unknown detected agent id as parsed wire data instead of a type assertion
* fix: explain the empty Hand Off list from the same host rows the picker shows
The usage-limit explanation read desktop preferences and the offered list, while the picker rows use the host's preferences. A host that disables every agent while the desktop enables the source agent then claimed the source family was available. Derive the explanation from the settled picker rows before the family filter, counting only rows that can open.
* fix(terminal): recognize labelled Claude composer frames
* fix(terminal): recognize Claude's default simulated cursor in the composer
Claude Code's default caret hides the native cursor and parks it at a
painted caret cell, so its composer (labelled or bare) was never detected:
the draft leaked into screen reads and automatic mail was not held.
Accept a hidden cursor on the Claude prompt only inside a complete composer
frame with the cursor right of the glyph. On the normal buffer the parked
cursor suffices (Claude drops the inverse caret paint on focus-out); on an
alternate or unknown buffer the cursor cell must also be inverse. Both facts
are read per call from xterm, only while the cursor is hidden. The closing
rule now comes from the row where the continuation walk stopped, removing
the extra rows-below scan. Codex and boxed composers keep rejecting hidden
cursors.
* test(terminal): scrub worktree name from blurred Claude fixture
* Make goal command entry consistent across supported chat agents
* Keep the composer goal mode binding local
* Open the goal chip only for the provider's built-in /goal
Carry the provider's built-in marker on slash-command rows (Claude's
initialize descriptors report it) through to the picker, and enter goal
mode from a picked /goal only when the session has host goals or the row
is the provider's own built-in. A user command or skill named goal keeps
its normal invocation, and bare /goal without host goals is sent as
typed again. Host goal objectives resolve pasted text before the /goal
prefix is stripped, as before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Mark Grok's built-in slash commands from its initialize response
Grok lists its own built-ins in the initialize response of each process.
Carry that onto the session command rows as the existing built-in marker,
so picking Grok's /goal opens the goal chip like Claude's. A row that
Grok tags as a skill or workflow never takes the marker, since a session
can close the built-in and let a same-named skill take its name.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Feed the Grok built-in command test a recorded initialize response
Replace the hand-trimmed initialize reply with Grok 1.0.70's recorded
initialize result, keeping its seven built-in command rows and removing
host-identifying metadata.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Type the composer send result as boolean in the IME composition test
The structured composer send now resolves to whether it was admitted.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Continue in Chat UI opens the chat before closing the terminal, and stops a CLI still starting
The chat tab now opens right after the terminal and is selected before the terminal closes; its create waits for proof the CLI exited, so the workspace never sits empty while a large conversation loads. The terminal is kept only when the host proves its shell runs nothing, so a CLI clicked before its first status update is stopped instead of left writing beside a chat that can't send.
* Continue in Chat UI asks before stopping another job, shows a chat already opening, and keeps a failed chat in place
A job started in the shell after the agent quit now goes through the terminal's running-process prompt instead of being stopped silently. A pane not bound to its terminal on this client is treated as unverifiable and closed, like any unverifiable read. When a chat for the same conversation is already opening, Continue shows it and keeps the terminal rather than handing the terminal into a launch it doesn't own. After the terminal closed, a failed start stays in the chat that took its place and says why once, including when the CLI's exit couldn't be confirmed, so the workspace is never left blank. A launch that ended before its tab opened reports its failure again, and a tab picked during the close stays selected.
* Continue in Chat UI asks before stopping a different agent, and answers a chat still opening without joining it
Only the conversation's own agent is stopped without the running-process prompt; another agent started in that shell is asked about like any other job. A chat already opening the same conversation is found before the new chat starts and shown (or named, from the tab menu) with the terminal kept, so an unconfirmed chat's create is never re-sent beside the running CLI. The in-chat message for an unconfirmed stop no longer points at a host connection. Test timestamps are fixed values.
Port the xterm, webgl, search, serialize and image source patches to upstream
08829c7. Where upstream fixed the same bug (#6204 mouse NaN guard, #6226 warm-up
cancel on dispose, #6235/#6237 serialize width clamp) take upstream and keep only
Orca's remaining delta (domContainer warm-up gate, clipped wide-cell blanking).
Bump mobile's webgl subset patch alongside. Drop serialize fuzz seed normal:5,
which now round-trips.
* fix(agent-status): match OSC 7501 spec length limits and progress parsing
* fix(agent-status): keep a capped OSC 7501 body whose ST splits across chunks
* fix(plugins): give no catalog release entry for a commit install would reject
The release entry printed by validate:plugin and orca plugin release-entry hashed the
committed HEAD tree without running install's file checks, so a commit with only
orca-plugin.json (declared theme files left uncommitted) got an entry whose install fails.
Run install's validatePluginInstallTree on the materialized tree, shared with install staging.
* refactor(plugins): return early per release-entry rejection; drop the temp tree path from the note
* fix(plugins): check the release entry the way install reads it
Read the committed manifest with install's 1 MiB bound, and round-trip the entry through
the catalog release schema and pluginCatalogReleaseError, so a field a catalog drops (e.g.
a contribution title over 160 chars) gives a note instead of an entry install refuses.
* fix(plugins): name the field and drop temp paths in release-entry notes
* Preserve typing during worktree chat handoff
* Keep chat drafts editable during worktree creation handoff
Transfer the live draft immediately and defer deleting its saved source
until the destination write succeeds, including after retries.
* Fix worktree creation handoff when an agent prompt is ready
Allow approval and question prompts to receive the handoff while
preserving unsent drafts and waiting for text composition to finish.
* Fix worktree creation handoff when chat cannot show an editor
* Prevent stalled worktree chat handoffs while preserving IME input
* feat(sidebar): render linked issues and tasks as compact reference rows
Issues, Linear and Jira tasks in the workspace hovercard now use the same
compact two-line row as PRs: state glyph, single-line title, provider mark,
identifier, source, labels summary, and a row actions menu.
* refactor(sidebar): share row menu shell and task actions across hovercards
- WorktreeCardRowActionsMenu backs both PR and task row menus
- Card tasks are expressed as attachments so single and linked rows share
labels, sources and the task menu
- getWorktreeCardTaskActions replaces duplicated copy-link and dismiss
wiring in WorktreeCardMeta and the activity hovercard
* fix(editor): release closed diff tabs' Monaco models under tab groups
Tab groups key each diff view by `${file.id}::${tab.id}`, and DiffViewer
derived its model-path owner prefix from that scoped key. Closed-tab cleanup
sweeps `diff:*:<file.id>` prefixes, so it never matched and every closed diff
tab leaked its original and modified TextModels. Each live model holds a
listener on Monaco's language-service change event, which surfaced as the
"potential listener LEAK" renderer error and an eventual renderer OOM.
DiffViewer now takes the open file ID as the model owner for its path prefix,
keeping the tab-scoped key for the per-view path segment and view-state cache.
* fix(editor): release Changes-mode diff models and pairs superseded across remounts
Changes mode gives edit tabs a DiffViewer whose models the edit-close path
never swept, so each closed Changes-mode tab leaked an original/modified
TextModel pair. ChangesModeView now passes the file ID as model owner, and
closing an edit tab that mounted a Changes view sweeps that owner's diff
prefixes (ordinary edit closes still skip registry enumeration).
DiffViewer remembers each view's last model paths per owner, so a remount
after content changed while unmounted disposes the superseded pair instead
of holding it until the file closes.
---------
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>