Commit Graph
18687 Commits
Author SHA1 Message Date
Neil c8112e643f docs(plugins): regenerate the manifest reference and schema for the priority wording (#37351) 2026-11-01 22:03:34 -08:00
Neil 999160b3db feat(plugins): commands[].replaces lets a mod take over a built-in action (#37316)
* plugins: commands[].replaces lets a worker command take over a built-in action

A worker command with replaces: <action> (the declarative action list,
engines >=1.4.248, needs main) runs in place of that built-in from its
shortcut, plugin aliases and the sidebar menus; a failed or silent (2 s)
worker falls back to Orca's own. The install screen names each replacement,
the plugin page has a per-action switch (settings.pluginReplacementsOff), and
when two plugins replace one action the one installed first wins and the
other's log says so. Update review counts replaces as new access.

* fix(plugins): narrow replaced action ids before looking up their titles

* plugins: harden command replaces after review

- Consent includes the replaced actions, so adding replaces to a dev folder
  needs a new approval; CLI approval and update review name each takeover.
- Shortcuts main intercepts (Tasks, Workspace Board, history), search with a
  selection and floating maximize now go through the replacement too.
- A worker that only starts after the 2 s fallback no longer runs the
  command as well: main refuses to start it past the deadline.
- A workspace-only command does not replace an action without a workspace.
- An update keeps the plugin's first install time, so it keeps its takeover.
- Drop the commands.run promise until Orca offers it.

* plugins: commands.run always runs the built-in, never a replacement

A5b's commands.run dispatches with the 'plugin-host' source; the app
dispatcher now builds handlers that run Orca's own action for it, so a
plugin that replaces an action and calls commands.run cannot loop. Docs and
schema describe wrapping the built-in with commands.run; regenerated.
2026-11-01 21:59:27 -08:00
OrcaWinandOrcaWin 8d1453fba9 test: remove 17 low-value test declarations (#37327)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-01 21:51:45 -08:00
Neil 3e52963a9a docs(plugins): fix custom-editor authoring papercuts from dogfood (#37337)
- shot --act: say edits only dirty the tab; end with Cmd/Ctrl+S to save
- manifest: compound suffixes like .sketch.json open by default
- cli.md: list plugin check's exit codes, including plugin_check_failed
- workflow: how to run and stop the dev app from a source checkout
- plugin create --help: real descriptions for --template/--publisher/--id/--name
- recipes: declaration file for vendored ESM libraries
2026-11-01 21:48:46 -08:00
NeilandClaude 2fb3a0975e feat(plugins): workers can open files and run built-in actions (#37323)
* feat(plugins): add editor.open and commands.run for full-access workers

Both ride the existing plugin view request channel (the one plugin open and
plugin shot use) to the one main window's renderer; headless serve and a
missing window answer unavailable. editor.open takes a path that stays
inside the workspace (Git worktrees and folders) and opens it as a tab
without switching what the person sees. commands.run takes only the built-in
contributes.commands action list and dispatches Orca's own handler, never a
plugin replacement. Both need workspace:write.

workspace.list now also lists project-group folder workspaces.

* fix(plugins): open folder workspaces and host-naming ids through Orca's window

workspace.open of a project-group folder asks the window to show it
(setActiveFolderWorkspace), since the runtime activates Git worktrees only.
editor.open resolves the identity: ids workspace.list now returns. commands.run
documents that it must call the raw built-in, never a replacement wrapper.

* plugins: regenerate the testing bundle and skill guides on the rebased host actions

Generated files only (lint-staged's oxlint refuses a commit with no lintable files).

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

* chore(plugins): regenerate the testing bundle on main

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 05:47:35 +00:00
NeilandClaude 544577fe02 feat(plugins): workers can list and open workspaces, create and read terminals, launch agents (#37312)
* feat(plugins): let full-access workers act on workspaces, terminals and agents

Host API minor 3 adds worker-only rows: workspace.list (workspace:read),
workspace.open (workspace:write), terminal.create (terminal:send),
terminal.read (terminal:read, at most 2,000 lines) and agent.launch
(agent:launch). Each dispatches the runtime RPC method the orca CLI calls,
in process with the CLI's identity, so it runs on the host that owns the
workspace (local, WSL or SSH) and covers folder workspaces. The new
capabilities arrive in 1.4.248; typings, docs and skill references are
generated from the table.

* fix(plugins): make workspace host actions safe to revoke, cancel and target

- Each runtime effect re-checks the plugin's approval right before the
  runtime acts (a dispatcher beforeInvoke hook, after its own waits), and a
  worker's cancel or exit now aborts the call's signal through to the runtime.
- agent.launch starts the agent's CLI in a background terminal and returns
  its handle at once; a prompt is delivered afterwards through the runtime's
  own reuse-terminal path, so the host call never waits for the composer.
- workspace.list adds project-group folder workspaces and returns ids that
  name their host (identity: keys), so twins on two hosts are never ambiguous.
- terminal.create passes presentation background; terminal.read counts a
  runtime-limited read as truncated. workspace.open refuses folders as
  unavailable until a window-side open lands.

* plugins: keep both API extensions and host-call tracking under the line limits after rebasing on the hooks

Worker-only status bar and workspace methods spread into PLUGIN_HOST_API through
one module, the in-flight host call map moves into PluginWorkerHostCalls, and
bindPluginRuntime builds the runtime RPC so main-process-plugins.ts keeps its
size; regenerated docs and typings.

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

* chore(plugins): regenerate after rebasing on the editor recipe and shots

* chore(plugins): regenerate after rebasing on the plugin shot recipe

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 05:44:29 +00:00
Brennan BensonandClaude Opus 5.5 1783cf58b1 Show agent sign-in notices before the first chat message (#37191)
* Show explicit agent sign-out facts before sending a chat

* Drop the Grok status process; keep listed models when Pi or Claude replies arrive late

Grok goes back to one listing process with no Grok-specific sign-out guess;
an agent-authored auth-required reply still reports notSignedIn. Pi reports
notSignedIn only when it has no provider and nothing listable. Once Claude's
required replies are in, the listing deadline returns them instead of failing.

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

* Drop the runner tests' timeout bumps, which sat below the default

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

* Type the endpoint test's launch env as a string map

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

* Drive the signed-out Claude re-probe trigger in the scaling contract

N and 10N distinct chats (distinct read claims, one account-scoped
Claude fingerprint) read with waitForListing against the real probe and
store on an injected clock: one listing before the held-reason TTL,
exactly one more once it is due (held until every read has decided),
none within the next TTL, each result carrying notSignedIn and models.

Checked locally that it bites: making heldReasonDue ignore the TTL fails
it (2 listings inside the TTL). Removing the in-flight dedup (the
probeDue isListing guard plus the lister's early return) does not: store
refresh then runs once per chat, but a newer flight replaces each queued
one behind the two admission slots, so still one process per TTL.

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

* Accept Claude control replies as main did, without a type field

The shared reader required type 'control_response' and prefiltered on
that text, dropping success frames main's model and slash-command
parsers accepted (the released-phone wire test's fake listing).
It now prefilters on request_id and keeps the structure limit and
success-only reading.

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

* Narrow the scaling contract's catalog result before reading models

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-01 21:42:07 -08:00
Brennan BensonandClaude Opus 5.5 06b8f7dc37 Keep Windows and Linux chat switching separate from worktree shortcuts (#37199)
* fix(shortcuts): give Windows and Linux chat switching a working default

* test(shortcuts): verify the worktree and chat chords independently

* fix(shortcuts): preserve agent newline keys while switching chat views

* fix(shortcuts): use Ctrl+Shift+F11 for Windows and Linux chat switching

The Ctrl+Alt+Shift+J default could not match non-Latin layouts or IME
input, because Ctrl+Alt chords skip the physical-key fallback that
protects AltGr characters. A function-key primary matches on every
layout; Ctrl+Shift+J stays as the alias where Switch worktree doesn't
claim it.

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

* fix(shortcuts): keep the chat toggle's own default alias when its shortcuts are edited

Saving any change to Switch between Chat UI and terminal on Windows and
Linux kept its untouched Ctrl+Shift+J alias, which the conflict check
then refused against the worktree palette's default, so replacing or
removing the primary failed. The check now exempts a chord per binding
when it is the action's own default and every other owner also holds it
as a default, so non-default explicit claims are still refused.

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

* test(shortcuts): expect the new Windows/Linux chat switch label

The context menus show the toggle's primary binding, which is now
Ctrl+Shift+F11 on Windows and Linux.

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-01 21:41:39 -08:00
Brennan Benson f9a4ba6225 Make phone Send follow the paired desktop setting (#37315)
* Make phone follow-up delivery follow paired desktop

* Keep the paired desktop's follow-up setting live through a server outage

The phone now keeps listening to the paired desktop's Queue follow-ups
setting while a chat is open, even when the workspace's server relay
drops, so a desktop toggle during the outage is used by the next Send.
The chat reuses the route's existing paired-host connection instead of
acquiring it again per chat, and the follow-up tests load and typecheck.

* Keep the chat lock-reason helper out of modules the desktop tests import

The overlay lock-reason helper moves to its own file so the shared
eligibility module no longer reaches the chat view, which pulled the
phone's UI types into the desktop end-to-end typecheck. The reconnect
test now drops and restores the client's own connection, and checks the
preference stream closes with it and reopens with one fresh read.
2026-11-01 21:40:47 -08:00
Brennan Benson 5d3dfe943e Tasks source summary, repo link and Filters row follow the list's actual source (#37226)
* Show the active GitHub list source in Tasks summary

* Tasks header, repo link and source row follow the list's resolved source

The Tasks summary could name the saved upstream project while the list came
from the fork, and the "Open in GitHub" link could open the other kind's
repository when the active kind's source was unresolved. A new search or an
Issues/PRs switch also blanked the source until the new fetch landed.

Compute the source summary once, after mode and item kind are known, from
the list's resolved sources (saved project identity no longer reaches the
formatter). The summary, the single-project repo link (strict active kind)
and the Filters source row share one display-only read: the current query's
entry, else the latest same-scope sibling while the query is fetchable.
Notices, errors, Retry, filter metadata and page rows still read only the
current query's entry. Projects, GitLab, Linear and Jira output is unchanged.

* Tasks source carries the shown entry across queries, read live

While a new search or Issues/PRs switch has no cached result yet, the Tasks
summary, repository link and Filters source row keep showing the source of
the entry they last showed for that project copy, read live from the cache,
instead of picking the newest cached sibling. A refresh that changes that
entry is followed, other queries' results never take over, a preference
change's eviction ends the claim, and a present unresolved result or a
too-large query makes no claim.

The display read now happens in the existing current-entry selection pass
(at most two cache reads per selected project, no cache scan), and the
summary re-translates when the language changes. Adds a SourceBar fixture
with a long fork slug at normal and narrow widths.

* Tasks source display perf contract measures each update on its own

Bound every measured update (another host's patch, a non-cache store write,
current and sibling fetches, a real preference eviction) at 10 and 100
selected rows and 50 or 500 cache entries: no cache enumeration, at most two
reads per row per selector call, a fixed selector-call budget equal across
sizes, no key rebuild on cache writes, linear row visits including pointer
upkeep, and reused output references for writes that change nothing shown.

* Use typed reads in the Tasks source display perf instrumentation

The static-analysis rule rejects Reflect.get; the cache and row proxies now
read by typed keys and count exactly the same reads and enumerations.

* Drop type assertions from the Tasks source display changes

Stage hooks return Object.assign(model, {...}) instead of casting the model
(same object, same writes, same type). The fixture and test stubs keep their
partial-model casts on one line each with a SAFETY reason, the row proxy uses
a type guard, and the filler work item is a typed fixture.
2026-11-01 21:40:33 -08:00
Neil 3b5eed8ca4 docs(plugins): point the editor and status bar recipes at plugin shot (#37332) 2026-11-01 21:40:07 -08:00
Brennan Benson 9ce35be4ff fix(native-chat): effort, speed and fast controls stay usable while the model list refreshes; dotted versions in id-derived model labels (#36870)
* fix(native-chat): running chats keep effort menus during model discovery; dotted versions in id-derived model labels

* fix(native-chat): options stay usable while a model list refreshes; dated preview ids keep their dashes

A running host listing no longer disables effort, speed or fast mode. Those
controls follow the data they draw from: before any list exists they show
the placeholder or a read-only saved effort, and once a list or the chat's
own answer is shown they stay usable through every refresh, for every agent.
The listing flag is now only the host's raw fact, which keeps a seed from
settling to the built-in list while discovery runs.

Id-derived labels no longer dot a zero-led part, so MM-DD preview dates
(gemini-2.5-flash-preview-05-20) stay dates.

* fix(native-chat): a model list refresh keeps the verified models and default effort already shown

While the host refreshes a Claude or Codex list, the chat keeps the model
ids an earlier finished listing verified that the refreshing rows still
contain, so a running Codex chat's CLI-default effort ("Medium") no longer
blanks from the pill and menu until the listing lands. Ids no finished
listing verified stay unverified, and a finished listing still replaces
the set.

* test(native-chat): type the listing-wait check without an assertion

* fix(model-labels): month-day dates after October stay undotted

* refactor(native-chat): listing-verified model ids get their own module
2026-11-01 21:39:37 -08:00
Brennan Benson 0bf96e0d7a Show a GitLab MR's pipeline result even when it has no jobs (#37177)
* Show failed pipelines and check suites without jobs

* Use the parent pipeline as the GitLab checks verdict

The MR's own pipeline status now leads the Source Control Checks panel,
the MR dialog and the phone checks badge whenever the details reply
carries it, even when the returned jobs are empty, partial or all
passing. Job rows and counts stay a separate breakdown. The parent
follows pipeline-level policy (required manual is pending, canceled and
skipped are not failures, mergeable manual is neutral) from one shared
rule, and it is a provider link rather than an expandable job, so it
never shows a details error with Retry. A failed parent leads the
triage strip and joins the Fix prompt by name and link.

Drops the GitHub zero-run failed check-suite rows from this change;
existing approval-required suite rows are unchanged.

* Name the GitLab pipeline outcome and keep its verdict in one rule

The host now settles the parent pipeline's verdict with the same rule
and reply that produce the MR status, so a manual pipeline on older
GitLab reads pending everywhere. The phone checks badge names the
pipeline outcome (Pipeline passed/failed/pending) instead of an
unlabeled one-item count; job wording without a parent is unchanged.
The desktop parent row's status is localized, and a failed MR-details
read still shows its error beside a kept parent with no jobs.

* Keep Fix for failed GitLab jobs and treat waiting_for_callback as pending

The triage strip shows again when a returned job failed under a passing
parent pipeline, so its Fix stays available; a failed parent still
leads. A waiting_for_callback pipeline now reads pending instead of
neutral, in the MR status and the parent pipeline alike.

* Mark the checks-panel action test fixture cast as safe

* Pause the pipeline row spinner offscreen and under reduced motion
2026-11-01 21:39:18 -08:00
Neil f908d2f40a feat(plugins): plugin shot --editor to screenshot and drive custom editors (#37302)
* 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
2026-11-01 21:38:04 -08:00
Neil 9b39188021 refactor: organize session option and conversation owners (#37289) 2026-11-01 21:33:42 -08:00
Neil 591f2acbc8 docs(plugins): add a rich-editor recipe and list frame theme variables in the skill (#37306)
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.
2026-11-01 21:30:37 -08:00
Neil fa7c3f95fe feat(plugins): check custom editors and test them with mockEditorHost (#37293)
* 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.
2026-11-01 21:28:13 -08:00
NeilandClaude 7989c05ad3 feat(plugins): agent tool-call hooks (Claude Code can block or rewrite) (#37303)
* 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>
2026-11-01 21:24:49 -08:00
NeilandClaude 60d262ebee feat(plugins): hooks for agent launches and prompts, plus plugin dev hook (#37295)
* 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>
2026-11-01 21:20:29 -08:00
Neil d9a070aa09 test(plugins): read the CSP probe's result path from its config, not argv's tail (#37329)
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.
2026-11-01 21:13:26 -08:00
NeilandClaude ac8c1125f0 feat(plugins): hooks that can change or block terminal sends and workspace creation (#37287)
* 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>
2026-11-01 21:12:57 -08:00
Neil 08813758b6 fix(themes): keep Match Terminal's host pill label at 4.5:1 on its fill (#37309)
* fix(themes): keep Match Terminal's host pill label at 4.5:1 on its fill

* fix(themes): clarify host pill comment
2026-11-01 20:56:41 -08:00
Neil 756c08724e fix(plugins): reject declared artifact paths whose case differs from the file (#37307)
* fix(plugins): reject declared artifact paths whose case differs from the file

* fix(plugins): name Unicode normalization in the path spelling error
2026-11-01 20:56:34 -08:00
Neil cddeccd161 test(plugins): drive the CSP refusal probe from script so it cannot hang on Linux (#37326)
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.
2026-11-01 20:50:44 -08:00
Neil ac48e72d77 fix(plugins): import custom editor host types from their own module to restore main lint (#37319) 2026-11-01 20:45:26 -08:00
Neil 892e8009f8 test(plugins): give the RPC approval fixture a statusBarItems list (#37324) 2026-11-01 20:44:58 -08:00
Brennan Benson 764bd0be44 Continue Fix checks and project actions through setup confirmation (#36539)
* 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
2026-11-01 20:39:09 -08:00
OrcaWinandOrcaWin 98d22faa8d test: remove 96 low-value test cases (#37296)
* test: remove 96 low-value test declarations

* test: update timeout baseline after audit903

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-01 20:33:06 -08:00
Neil a7ae68fc6f ci: full checks on main pushes, changed-file lint on PRs, Slack alert when main breaks (#37282)
* ci: full checks on main pushes, changed-file lint on PRs, Slack alert when main breaks

PR Checks now also runs on every push to main, with each commit in its own
concurrency group so main runs never cancel one another. On a push the path
router gets an empty diff, which already fails closed to every job, and the
input-scoped steps (localization extraction, VM rollback replay, orcad smoke)
run in full. Advisory E2E lanes stay PR-only.

On PRs the per-file passes (root oxlint, JS-plugin oxlint, anti-slop, ast-grep
and oxfmt) check only the changed paths. Everything is checked when the diff
cannot be resolved, a lint toolchain input changes (oxlint/oxfmt/ast-grep
configs and plugins, package.json, the lockfile, .gitignore, pr.yml), or more
than 1500 paths change. Whole-program checks stay unscoped.

A new workflow follows completed main runs and posts to the release Slack
webhook only when main turns red, and once when it turns green again.

* ci: alert once per main health streak and tolerate a malformed Slack mapping
2026-11-01 20:30:03 -08:00
Neil a9b1041ecc feat(plugins): status bar items plugins can update live (#37291)
* 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
2026-11-02 04:26:55 +00:00
Neil fbe891ce8b fix(plugins): skip nested packages in dev folders and summarize blocked frame requests (#37300)
* 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.
2026-11-02 04:21:03 +00:00
Neil 4a0ee07bd2 feat(plugins): orca.document.session() for custom editors (#37285)
* 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
2026-11-02 04:20:01 +00:00
Neil bdd997f71f test(cross-version): load moved modules from wherever each release defines them (#37297)
* test(cross-version): load moved modules from wherever each release defines them

v1.4.246 is the first release cut after #36909, #36979, #37036 and #37070 moved
the agent-session modules, so suites that imported the old paths from the
newest release failed with ERR_MODULE_NOT_FOUND for every PR running
cross-version wire. importReleaseCheckoutModule now resolves a moved module to
the path the checked-out release ships, from one table of current and earlier
locations, so both the rolling baseline and older pinned refs load. The
released-chat writers drop their own per-module existsSync fallbacks for it.

v1.4.246 also ships outcome storage, so it reads chats this build writes and
closes its own rows with the outcomes marker. The downgrade test pins its
refusing window to the two releases before outcome storage, and the upgrade
test leaves the marker to the outcome reader, which restoreJournal checks.

* test(cross-version): find a moved module by name in the release instead of a path table

A table of earlier paths is a list of stale src literals the path-literal ratchet rightly refuses, and needs an entry for every future move. The release checkout itself says where it ships each module: the one file with that name under the same source root. Prunes the three released-chat-writers entries the ratchet baseline grandfathered.

* test(cross-version): pin the chat-upgrade writers to the releases before outcome storage

v1.4.246 keeps a chat in outcome tables, so its rows hold only the marker and there is no row upgrade to carry on; the newest release stays the reader this build must match.
2026-11-01 20:19:25 -08:00
Neil 51f302167e feat(plugins): let a custom editor require the desktop app's isolated frame (#37298)
* 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.
2026-11-01 20:15:41 -08:00
Neil e74b40bd4e fix(relay): dead-man boot grace for a freshly booted reserve cell (#37225)
* fix(relay): give a freshly booted reserve cell a dead-man boot grace until its first director contact

A reserve cell's dead-man starts its 60-120 s window when the switch file applies, but no
placing director can reach a new process until its load balancer routes to it. On the
2026-11-01 production same-cap rolls that took up to about 110 s after the switch file
applied; c10 (104 s window) and c16 (71 s) tripped 1-4 s before the first director poll
landed, latched into db, and failed their roll after a clean drain and restart.

Until the process's first contact (a reserver=1 feed poll or a real booking), the window is
the hashed window plus 180 s, matching autohealing's initial delay. After the first contact
nothing changes. A process that never hears from a placing director still falls back to db,
up to three minutes later than before.

* test(relay): assert the server wires the dead-man boot grace

* docs(relay): state the boot gap behind the dead-man grace as inferred, not measured
2026-11-01 20:15:37 -08:00
Neil 1cc843eef4 refactor(plugins): move custom editor host types to their own file (#37314) 2026-11-01 20:08:01 -08:00
Brennan Benson a08ac11607 Show draft and restricted GitHub Project items (#37224)
* 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.
2026-11-01 20:05:48 -08:00
Brennan Benson 54b496cef1 fix(cli): open ordinary directories as folder projects (#37232)
* fix(cli): open ordinary directories as folder projects

* fix(cli): explain the non-Git layout in the repo add next step
2026-11-01 19:49:07 -08:00
Brennan Benson 8cbd822e4c Keep readable conversations out of Session History warnings (#37211)
* 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.
2026-11-01 19:48:40 -08:00
Neil ba50135f9c fix(plugins): hand over pending custom-editor edits before closing or reloading (#37278)
* 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
2026-11-01 19:46:57 -08:00
Neil 6e9a5b91c2 fix(terminal): stop stray escape tails like "2C" printing after returning to a busy pane (#37269)
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.
2026-11-01 19:44:51 -08:00
Brennan BensonandMax Hsu 8e326f2629 Keep GitHub task assignees and rows current after dialog edits (#34245)
* 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>
2026-11-01 19:34:54 -08:00
Neil 5e33efa36a fix(plugins): keep custom editor documents in step with accepted edits (#37277)
* 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
2026-11-01 19:31:08 -08:00
Brennan Benson 5020bfead6 Keep Session History open when a chat workspace was deleted (#37200)
* 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
2026-11-01 19:31:05 -08:00
Neil 0ce6fa17c8 fix(sidebar): keep the host pill outlined on the selected workspace card (#37294)
* 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
2026-11-01 19:27:49 -08:00
Neil 37ce11aff1 fix(ghostty): import the primary font-family from a repeated fallback list (#37292)
* 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
2026-11-01 19:27:43 -08:00
OrcaWinandOrcaWin 263d3b16aa test: remove 104 low-value test declarations (#37290)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-01 19:23:32 -08:00
Brennan Benson 347e9ca5d3 Phone Automations keeps every automation it can read when a newer desktop sends unknown values (#37169)
* fix(mobile): keep readable automations when a newer host sends unknown values

The phone read the host's automation list, run history and editor inventories all-or-nothing, so one row with a value this build doesn't know (a newer agent, surface or host kind) blanked the whole Automations screen. Read each list row by row with the shared salvage reader and the desktop reader's ownership rules, keep automations whose agent this build doesn't know (labelled by id, validated by the host), and read an unknown default agent as unset.

Tracked in STA-13196.

* fix(mobile): keep automations with a newer surface, summary or run host

Plan check follow-up: the phone never writes the surface, so an unknown one keeps its row and skips the phone-side argument pre-check like an unknown agent; an unreadable last-run summary reads as none, as on desktop; a run's host id is display-only, so a newer host kind keeps its run.

Tracked in STA-13196.

* fix(mobile): keep an unknown saved agent out of writes and never read an unreadable run or pane as gone
2026-11-01 19:23:07 -08:00
Brennan Benson a1d30fbd77 fix(mobile): keep the keyboard open when a tab tap opens its details card (#36866)
* fix(mobile): keep the keyboard open when a tab tap opens its details card

Tracked in STA-13199.

* fix(mobile): keep the keyboard open when the session title opens its details card

The session title opens the same details card as a tab, but sat outside every
keep-focus container, so its tap moved focus off a focused composer or terminal
input and closed the soft keyboard. Attach the existing keep-focus helper to the
title button so the tap leaves the field focused, as tab taps already do.

* fix(mobile): keep the keep-focus helper safe where there is no DOM

The session title now attaches the keep-focus helper, and the title renders in
renderers without a DOM, where checking against HTMLElement throws. Check that
HTMLElement exists first, as other mobile code already does.

* test(mobile): use a type-only namespace import for the react-native mock type
2026-11-01 19:22:42 -08:00
Brennan Benson f037753ee3 Keep chat pickers usable while options recover (#37168)
* 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.
2026-11-01 19:19:11 -08:00